| diff --git a/source/common/Makefile.in b/source/common/Makefile.in |
| index 6dd4c4d..b187af6 100644 |
| --- a/source/common/Makefile.in |
| +++ b/source/common/Makefile.in |
| @@ -84,7 +84,7 @@ uhash.o uhash_us.o uenum.o ustrenum.o uvector.o ustack.o uvectr32.o uvectr64.o \ |
| ucnv.o ucnv_bld.o ucnv_cnv.o ucnv_io.o ucnv_cb.o ucnv_err.o ucnvlat1.o \ |
| ucnv_u7.o ucnv_u8.o ucnv_u16.o ucnv_u32.o ucnvscsu.o ucnvbocu.o \ |
| ucnv_ext.o ucnvmbcs.o ucnv2022.o ucnvhz.o ucnv_lmb.o ucnvisci.o ucnvdisp.o ucnv_set.o ucnv_ct.o \ |
| -uresbund.o ures_cnv.o uresdata.o resbund.o resbund_cnv.o \ |
| +resource.o uresbund.o ures_cnv.o uresdata.o resbund.o resbund_cnv.o \ |
| messagepattern.o ucat.o locmap.o uloc.o locid.o locutil.o locavailable.o locdispnames.o loclikely.o locresdata.o \ |
| bytestream.o stringpiece.o \ |
| stringtriebuilder.o bytestriebuilder.o \ |
| diff --git a/source/common/common.vcxproj b/source/common/common.vcxproj |
| index 24bf4de..18ca9a3 100644 |
| --- a/source/common/common.vcxproj |
| +++ b/source/common/common.vcxproj |
| @@ -379,7 +379,8 @@ |
| <ClCompile Include="uloc_tag.c" /> |
| <ClCompile Include="ures_cnv.c" /> |
| <ClCompile Include="uresbund.cpp" /> |
| - <ClCompile Include="uresdata.c" /> |
| + <ClCompile Include="uresdata.cpp" /> |
| + <ClCompile Include="resource.cpp" /> |
| <ClCompile Include="caniter.cpp"> |
| </ClCompile> |
| <ClCompile Include="filterednormalizer2.cpp" /> |
| @@ -1135,6 +1136,7 @@ |
| <ClInclude Include="uresdata.h" /> |
| <ClInclude Include="uresimp.h" /> |
| <ClInclude Include="ureslocs.h" /> |
| + <ClInclude Include="resource.h" /> |
| <CustomBuild Include="unicode\caniter.h"> |
| <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">copy "%(FullPath)" ..\..\include\unicode |
| </Command> |
| diff --git a/source/common/common.vcxproj.filters b/source/common/common.vcxproj.filters |
| index 01b0625..4a4954b 100644 |
| --- a/source/common/common.vcxproj.filters |
| +++ b/source/common/common.vcxproj.filters |
| @@ -343,7 +343,10 @@ |
| <ClCompile Include="uresbund.cpp"> |
| <Filter>locales & resources</Filter> |
| </ClCompile> |
| - <ClCompile Include="uresdata.c"> |
| + <ClCompile Include="uresdata.cpp"> |
| + <Filter>locales & resources</Filter> |
| + </ClCompile> |
| + <ClCompile Include="resource.cpp"> |
| <Filter>locales & resources</Filter> |
| </ClCompile> |
| <ClCompile Include="caniter.cpp"> |
| @@ -769,6 +772,9 @@ |
| <ClInclude Include="uresdata.h"> |
| <Filter>locales & resources</Filter> |
| </ClInclude> |
| + <ClInclude Include="resource.h"> |
| + <Filter>locales & resources</Filter> |
| + </ClInclude> |
| <ClInclude Include="uresimp.h"> |
| <Filter>locales & resources</Filter> |
| </ClInclude> |
| diff --git a/source/common/listformatter.cpp b/source/common/listformatter.cpp |
| index 6dbf6c5..4941fa2 100644 |
| --- a/source/common/listformatter.cpp |
| +++ b/source/common/listformatter.cpp |
| @@ -1,7 +1,7 @@ |
| /* |
| ******************************************************************************* |
| * |
| -* Copyright (C) 2013-2014, International Business Machines |
| +* Copyright (C) 2013-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| * |
| ******************************************************************************* |
| @@ -36,11 +36,12 @@ ListFormatInternal( |
| const UnicodeString& two, |
| const UnicodeString& start, |
| const UnicodeString& middle, |
| - const UnicodeString& end) : |
| - twoPattern(two), |
| - startPattern(start), |
| - middlePattern(middle), |
| - endPattern(end) {} |
| + const UnicodeString& end, |
| + UErrorCode &errorCode) : |
| + twoPattern(two, 2, 2, errorCode), |
| + startPattern(start, 2, 2, errorCode), |
| + middlePattern(middle, 2, 2, errorCode), |
| + endPattern(end, 2, 2, errorCode) {} |
| |
| ListFormatInternal(const ListFormatData &data) : |
| twoPattern(data.twoPattern), |
| @@ -191,11 +192,15 @@ static ListFormatInternal* loadListFormatInternal( |
| if (U_FAILURE(errorCode)) { |
| return NULL; |
| } |
| - ListFormatInternal* result = new ListFormatInternal(two, start, middle, end); |
| + ListFormatInternal* result = new ListFormatInternal(two, start, middle, end, errorCode); |
| if (result == NULL) { |
| errorCode = U_MEMORY_ALLOCATION_ERROR; |
| return NULL; |
| } |
| + if (U_FAILURE(errorCode)) { |
| + delete result; |
| + return NULL; |
| + } |
| return result; |
| } |
| |
| diff --git a/source/common/resource.cpp b/source/common/resource.cpp |
| new file mode 100644 |
| index 0000000..7a4c418 |
| --- /dev/null |
| +++ b/source/common/resource.cpp |
| @@ -0,0 +1,60 @@ |
| +/* |
| +******************************************************************************* |
| +* Copyright (C) 2015, International Business Machines |
| +* Corporation and others. All Rights Reserved. |
| +******************************************************************************* |
| +* resource.cpp |
| +* |
| +* created on: 2015nov04 |
| +* created by: Markus W. Scherer |
| +*/ |
| + |
| +#include "resource.h" |
| + |
| +#include "unicode/utypes.h" |
| +#include "unicode/uobject.h" |
| +#include "unicode/ures.h" |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +ResourceValue::~ResourceValue() {} |
| + |
| + |
| +ResourceArraySink::~ResourceArraySink() {} |
| + |
| +void ResourceArraySink::put( |
| + int32_t /*index*/, const ResourceValue & /*value*/, UErrorCode & /*errorCode*/) {} |
| + |
| +ResourceArraySink *ResourceArraySink::getOrCreateArraySink( |
| + int32_t /*index*/, int32_t /*size*/, UErrorCode & /*errorCode*/) { |
| + return NULL; |
| +} |
| + |
| +ResourceTableSink *ResourceArraySink::getOrCreateTableSink( |
| + int32_t /*index*/, int32_t /*initialSize*/, UErrorCode & /*errorCode*/) { |
| + return NULL; |
| +} |
| + |
| +void ResourceArraySink::leave(UErrorCode & /*errorCode*/) {} |
| + |
| + |
| +ResourceTableSink::~ResourceTableSink() {} |
| + |
| +void ResourceTableSink::put( |
| + const char * /*key*/, const ResourceValue & /*value*/, UErrorCode & /*errorCode*/) {} |
| + |
| +void ResourceTableSink::putNoFallback(const char * /*key*/, UErrorCode & /*errorCode*/) {} |
| + |
| +ResourceArraySink *ResourceTableSink::getOrCreateArraySink( |
| + const char * /*key*/, int32_t /*size*/, UErrorCode & /*errorCode*/) { |
| + return NULL; |
| +} |
| + |
| +ResourceTableSink *ResourceTableSink::getOrCreateTableSink( |
| + const char * /*key*/, int32_t /*initialSize*/, UErrorCode & /*errorCode*/) { |
| + return NULL; |
| +} |
| + |
| +void ResourceTableSink::leave(UErrorCode & /*errorCode*/) {} |
| + |
| +U_NAMESPACE_END |
| diff --git a/source/common/resource.h b/source/common/resource.h |
| new file mode 100644 |
| index 0000000..042e298 |
| --- /dev/null |
| +++ b/source/common/resource.h |
| @@ -0,0 +1,248 @@ |
| +/* |
| +******************************************************************************* |
| +* Copyright (C) 2015, International Business Machines |
| +* Corporation and others. All Rights Reserved. |
| +******************************************************************************* |
| +* resource.h |
| +* |
| +* created on: 2015nov04 |
| +* created by: Markus W. Scherer |
| +*/ |
| + |
| +#ifndef __URESOURCE_H__ |
| +#define __URESOURCE_H__ |
| + |
| +/** |
| + * \file |
| + * \brief ICU resource bundle key and value types. |
| + */ |
| + |
| +// Note: Ported from ICU4J class UResource and its nested classes, |
| +// but the C++ classes are separate, not nested. |
| + |
| +// We use the Resource prefix for C++ classes, as usual. |
| +// The UResource prefix would be used for C types. |
| + |
| +#include "unicode/utypes.h" |
| +#include "unicode/unistr.h" |
| +#include "unicode/ures.h" |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +class ResourceTableSink; |
| + |
| +// Note: In C++, we use const char * pointers for keys, |
| +// rather than an abstraction like Java UResource.Key. |
| + |
| +/** |
| + * Represents a resource bundle item's value. |
| + * Avoids object creations as much as possible. |
| + * Mutable, not thread-safe. |
| + */ |
| +class U_COMMON_API ResourceValue : public UObject { |
| +public: |
| + virtual ~ResourceValue(); |
| + |
| + /** |
| + * @return ICU resource type, for example, URES_STRING |
| + */ |
| + virtual UResType getType() const = 0; |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not a string resource. |
| + * |
| + * @see ures_getString() |
| + */ |
| + virtual const UChar *getString(int32_t &length, UErrorCode &errorCode) const = 0; |
| + |
| + inline UnicodeString getUnicodeString(UErrorCode &errorCode) const { |
| + int32_t len = 0; |
| + const UChar *r = getString(len, errorCode); |
| + return UnicodeString(TRUE, r, len); |
| + } |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not an alias resource. |
| + */ |
| + virtual const UChar *getAliasString(int32_t &length, UErrorCode &errorCode) const = 0; |
| + |
| + inline UnicodeString getAliasUnicodeString(UErrorCode &errorCode) const { |
| + int32_t len = 0; |
| + const UChar *r = getAliasString(len, errorCode); |
| + return UnicodeString(TRUE, r, len); |
| + } |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not an integer resource. |
| + * |
| + * @see ures_getInt() |
| + */ |
| + virtual int32_t getInt(UErrorCode &errorCode) const = 0; |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not an integer resource. |
| + * |
| + * @see ures_getUInt() |
| + */ |
| + virtual uint32_t getUInt(UErrorCode &errorCode) const = 0; |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not an intvector resource. |
| + * |
| + * @see ures_getIntVector() |
| + */ |
| + virtual const int32_t *getIntVector(int32_t &length, UErrorCode &errorCode) const = 0; |
| + |
| + /** |
| + * Sets U_RESOURCE_TYPE_MISMATCH if this is not a binary-blob resource. |
| + * |
| + * @see ures_getBinary() |
| + */ |
| + virtual const uint8_t *getBinary(int32_t &length, UErrorCode &errorCode) const = 0; |
| + |
| +protected: |
| + ResourceValue() {} |
| + |
| +private: |
| + ResourceValue(const ResourceValue &); // no copy constructor |
| + ResourceValue &operator=(const ResourceValue &); // no assignment operator |
| +}; |
| + |
| +/** |
| + * Sink for ICU resource array contents. |
| + * The base class does nothing. |
| + * |
| + * Nested arrays and tables are stored as nested sinks, |
| + * never put() as ResourceValue items. |
| + */ |
| +class U_COMMON_API ResourceArraySink : public UObject { |
| +public: |
| + ResourceArraySink() {} |
| + virtual ~ResourceArraySink(); |
| + |
| + /** |
| + * Adds a value from a resource array. |
| + * |
| + * @param index of the resource array item |
| + * @param value resource value |
| + */ |
| + virtual void put(int32_t index, const ResourceValue &value, UErrorCode &errorCode); |
| + |
| + /** |
| + * Returns a nested resource array at the array index as another sink. |
| + * Creates the sink if none exists for the key. |
| + * Returns NULL if nested arrays are not supported. |
| + * The default implementation always returns NULL. |
| + * |
| + * This sink (not the caller) owns the nested sink. |
| + * |
| + * @param index of the resource array item |
| + * @param size number of array items |
| + * @return nested-array sink, or NULL |
| + */ |
| + virtual ResourceArraySink *getOrCreateArraySink( |
| + int32_t index, int32_t size, UErrorCode &errorCode); |
| + |
| + /** |
| + * Returns a nested resource table at the array index as another sink. |
| + * Creates the sink if none exists for the key. |
| + * Returns NULL if nested tables are not supported. |
| + * The default implementation always returns NULL. |
| + * |
| + * This sink (not the caller) owns the nested sink. |
| + * |
| + * @param index of the resource array item |
| + * @param initialSize size hint for creating the sink if necessary |
| + * @return nested-table sink, or NULL |
| + */ |
| + virtual ResourceTableSink *getOrCreateTableSink( |
| + int32_t index, int32_t initialSize, UErrorCode &errorCode); |
| + |
| + /** |
| + * "Leaves" the array. |
| + * Indicates that all of the resources and sub-resources of the current array |
| + * have been enumerated. |
| + */ |
| + virtual void leave(UErrorCode &errorCode); |
| + |
| +private: |
| + ResourceArraySink(const ResourceArraySink &); // no copy constructor |
| + ResourceArraySink &operator=(const ResourceArraySink &); // no assignment operator |
| +}; |
| + |
| +/** |
| + * Sink for ICU resource table contents. |
| + * The base class does nothing. |
| + * |
| + * Nested arrays and tables are stored as nested sinks, |
| + * never put() as ResourceValue items. |
| + */ |
| +class U_COMMON_API ResourceTableSink : public UObject { |
| +public: |
| + ResourceTableSink() {} |
| + virtual ~ResourceTableSink(); |
| + |
| + /** |
| + * Adds a key-value pair from a resource table. |
| + * |
| + * @param key resource key string |
| + * @param value resource value |
| + */ |
| + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode); |
| + |
| + /** |
| + * Adds a no-fallback/no-inheritance marker for this key. |
| + * Used for CLDR no-fallback data values of (three empty-set symbols)=={2205, 2205, 2205} |
| + * when enumerating tables with fallback from the specific resource bundle to root. |
| + * |
| + * The default implementation does nothing. |
| + * |
| + * @param key to be removed |
| + */ |
| + virtual void putNoFallback(const char *key, UErrorCode &errorCode); |
| + |
| + /** |
| + * Returns a nested resource array for the key as another sink. |
| + * Creates the sink if none exists for the key. |
| + * Returns NULL if nested arrays are not supported. |
| + * The default implementation always returns NULL. |
| + * |
| + * This sink (not the caller) owns the nested sink. |
| + * |
| + * @param key resource key string |
| + * @param size number of array items |
| + * @return nested-array sink, or NULL |
| + */ |
| + virtual ResourceArraySink *getOrCreateArraySink( |
| + const char *key, int32_t size, UErrorCode &errorCode); |
| + |
| + /** |
| + * Returns a nested resource table for the key as another sink. |
| + * Creates the sink if none exists for the key. |
| + * Returns NULL if nested tables are not supported. |
| + * The default implementation always returns NULL. |
| + * |
| + * This sink (not the caller) owns the nested sink. |
| + * |
| + * @param key resource key string |
| + * @param initialSize size hint for creating the sink if necessary |
| + * @return nested-table sink, or NULL |
| + */ |
| + virtual ResourceTableSink *getOrCreateTableSink( |
| + const char *key, int32_t initialSize, UErrorCode &errorCode); |
| + |
| + /** |
| + * "Leaves" the table. |
| + * Indicates that all of the resources and sub-resources of the current table |
| + * have been enumerated. |
| + */ |
| + virtual void leave(UErrorCode &errorCode); |
| + |
| +private: |
| + ResourceTableSink(const ResourceTableSink &); // no copy constructor |
| + ResourceTableSink &operator=(const ResourceTableSink &); // no assignment operator |
| +}; |
| + |
| +U_NAMESPACE_END |
| + |
| +#endif |
| diff --git a/source/common/simplepatternformatter.cpp b/source/common/simplepatternformatter.cpp |
| index 0cac2ec..abaaea9 100644 |
| --- a/source/common/simplepatternformatter.cpp |
| +++ b/source/common/simplepatternformatter.cpp |
| @@ -1,6 +1,6 @@ |
| /* |
| ****************************************************************************** |
| -* Copyright (C) 2014, International Business Machines |
| +* Copyright (C) 2014-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ****************************************************************************** |
| * simplepatternformatter.cpp |
| @@ -149,6 +149,17 @@ SimplePatternFormatter::SimplePatternFormatter(const UnicodeString &pattern) : |
| compile(pattern, status); |
| } |
| |
| +SimplePatternFormatter::SimplePatternFormatter(const UnicodeString &pattern, |
| + int32_t min, int32_t max, |
| + UErrorCode &errorCode) |
| + : noPlaceholders(), |
| + placeholders(), |
| + placeholderSize(0), |
| + placeholderCount(0), |
| + firstPlaceholderReused(FALSE) { |
| + compileMinMaxPlaceholders(pattern, min, max, errorCode); |
| +} |
| + |
| SimplePatternFormatter::SimplePatternFormatter( |
| const SimplePatternFormatter &other) : |
| noPlaceholders(other.noPlaceholders), |
| @@ -182,8 +193,10 @@ SimplePatternFormatter &SimplePatternFormatter::operator=( |
| SimplePatternFormatter::~SimplePatternFormatter() { |
| } |
| |
| -UBool SimplePatternFormatter::compile( |
| - const UnicodeString &pattern, UErrorCode &status) { |
| +UBool SimplePatternFormatter::compileMinMaxPlaceholders( |
| + const UnicodeString &pattern, |
| + int32_t min, int32_t max, |
| + UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| @@ -224,6 +237,7 @@ UBool SimplePatternFormatter::compile( |
| idBuilder.add(ch); |
| } else if (ch == 0x7D && idBuilder.isValid()) { |
| if (!addPlaceholder(idBuilder.getId(), len)) { |
| + noPlaceholders.releaseBuffer(0); |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return FALSE; |
| } |
| @@ -255,6 +269,10 @@ UBool SimplePatternFormatter::compile( |
| break; |
| } |
| noPlaceholders.releaseBuffer(len); |
| + if (placeholderCount < min || max < placeholderCount) { |
| + status = U_ILLEGAL_ARGUMENT_ERROR; |
| + return FALSE; |
| + } |
| return TRUE; |
| } |
| |
| diff --git a/source/common/simplepatternformatter.h b/source/common/simplepatternformatter.h |
| index 6740dc9..782a29c 100644 |
| --- a/source/common/simplepatternformatter.h |
| +++ b/source/common/simplepatternformatter.h |
| @@ -1,6 +1,6 @@ |
| /* |
| ****************************************************************************** |
| -* Copyright (C) 2014, International Business Machines |
| +* Copyright (C) 2014-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ****************************************************************************** |
| * simplepatternformatter.h |
| @@ -52,12 +52,22 @@ public: |
| SimplePatternFormatter(); |
| |
| /** |
| - * Construct from a pattern. Will never fail if pattern has three or |
| + * Constructs from a pattern. Will never fail if pattern has three or |
| * fewer placeholders in it. |
| */ |
| explicit SimplePatternFormatter(const UnicodeString& pattern); |
| |
| /** |
| + * Constructs from a pattern. Will never fail if pattern has three or |
| + * fewer placeholders in it. |
| + * |
| + * @param min The pattern must have at least this many placeholders. |
| + * @param max The pattern must have at most this many placeholders. |
| + */ |
| + SimplePatternFormatter(const UnicodeString& pattern, int32_t min, int32_t max, |
| + UErrorCode &errorCode); |
| + |
| + /** |
| * Copy constructor. |
| */ |
| SimplePatternFormatter(const SimplePatternFormatter& other); |
| @@ -79,7 +89,22 @@ public: |
| * there are three or fewer placeholders in pattern. May fail with |
| * U_MEMORY_ALLOCATION_ERROR if there are more than three placeholders. |
| */ |
| - UBool compile(const UnicodeString &pattern, UErrorCode &status); |
| + UBool compile(const UnicodeString &pattern, UErrorCode &status) { |
| + return compileMinMaxPlaceholders(pattern, 0, INT32_MAX, status); |
| + } |
| + |
| + /** |
| + * Compiles pattern and makes this object represent pattern. |
| + * |
| + * Returns TRUE on success; FALSE on failure. Will not fail if |
| + * there are three or fewer placeholders in pattern. May fail with |
| + * U_MEMORY_ALLOCATION_ERROR if there are more than three placeholders. |
| + * |
| + * @param min The pattern must have at least this many placeholders. |
| + * @param max The pattern must have at most this many placeholders. |
| + */ |
| + UBool compileMinMaxPlaceholders(const UnicodeString &pattern, |
| + int32_t min, int32_t max, UErrorCode &status); |
| |
| /** |
| * Returns (maxPlaceholderId + 1). For example |
| diff --git a/source/common/uresbund.cpp b/source/common/uresbund.cpp |
| index e74afb8..b2d071a 100644 |
| --- a/source/common/uresbund.cpp |
| +++ b/source/common/uresbund.cpp |
| @@ -1882,6 +1882,121 @@ ures_getByKeyWithFallback(const UResourceBundle *resB, |
| return fillIn; |
| } |
| |
| +namespace { |
| + |
| +void getAllContainerItemsWithFallback( |
| + const UResourceBundle *bundle, ResourceDataValue &value, |
| + ResourceArraySink *arraySink, ResourceTableSink *tableSink, |
| + UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return; } |
| + // We recursively enumerate child-first, |
| + // only storing parent items in the absence of child items. |
| + // We store a placeholder value for the no-fallback/no-inheritance marker |
| + // to prevent a parent item from being stored. |
| + // |
| + // It would be possible to recursively enumerate parent-first, |
| + // overriding parent items with child items. |
| + // When we see the no-fallback/no-inheritance marker, |
| + // then we would remove the parent's item. |
| + // We would deserialize parent values even though they are overridden in a child bundle. |
| + UResType expectedType = arraySink != NULL ? URES_ARRAY : URES_TABLE; |
| + if (ures_getType(bundle) == expectedType) { |
| + value.pResData = &bundle->fResData; |
| + if (arraySink != NULL) { |
| + ures_getAllArrayItems(&bundle->fResData, bundle->fRes, value, *arraySink, errorCode); |
| + } else /* tableSink != NULL */ { |
| + ures_getAllTableItems(&bundle->fResData, bundle->fRes, value, *tableSink, errorCode); |
| + } |
| + } |
| + UResourceDataEntry *entry = bundle->fData->fParent; |
| + if (entry != NULL && U_SUCCESS(entry->fBogus)) { |
| + // We might try to query the sink whether |
| + // any fallback from the parent bundle is still possible. |
| + |
| + // Turn the parent UResourceDataEntry into a UResourceBundle, |
| + // much like in ures_openWithType(). |
| + // TODO: See if we can refactor ures_getByKeyWithFallback() |
| + // and pull out an inner function that takes and returns a UResourceDataEntry |
| + // so that we need not create UResourceBundle objects. |
| + UResourceBundle parentBundle; |
| + ures_initStackObject(&parentBundle); |
| + parentBundle.fTopLevelData = parentBundle.fData = entry; |
| + // TODO: What is the difference between bundle fData and fTopLevelData? |
| + uprv_memcpy(&parentBundle.fResData, &entry->fData, sizeof(ResourceData)); |
| + // TODO: Try to replace bundle.fResData with just using bundle.fData->fData. |
| + parentBundle.fHasFallback = !parentBundle.fResData.noFallback; |
| + parentBundle.fIsTopLevel = TRUE; |
| + parentBundle.fRes = parentBundle.fResData.rootRes; |
| + parentBundle.fSize = res_countArrayItems(&(parentBundle.fResData), parentBundle.fRes); |
| + parentBundle.fIndex = -1; |
| + entryIncrease(entry); |
| + |
| + // Look up the container item in the parent bundle. |
| + UResourceBundle containerBundle; |
| + ures_initStackObject(&containerBundle); |
| + const UResourceBundle *rb; |
| + if (bundle->fResPath == NULL || *bundle->fResPath == 0) { |
| + rb = &parentBundle; |
| + } else { |
| + rb = ures_getByKeyWithFallback(&parentBundle, bundle->fResPath, |
| + &containerBundle, &errorCode); |
| + } |
| + if (U_SUCCESS(errorCode) && ures_getType(rb) == expectedType) { |
| + getAllContainerItemsWithFallback(rb, value, |
| + arraySink, tableSink, errorCode); |
| + } |
| + ures_close(&containerBundle); |
| + ures_close(&parentBundle); |
| + } |
| +} |
| + |
| +void getAllContainerItemsWithFallback( |
| + const UResourceBundle *bundle, const char *path, |
| + ResourceArraySink *arraySink, ResourceTableSink *tableSink, |
| + UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return; } |
| + if (path == NULL) { |
| + errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| + return; |
| + } |
| + UResourceBundle stackBundle; |
| + ures_initStackObject(&stackBundle); |
| + const UResourceBundle *rb; |
| + if (*path == 0) { |
| + // empty path |
| + rb = bundle; |
| + } else { |
| + rb = ures_getByKeyWithFallback(bundle, path, &stackBundle, &errorCode); |
| + if (U_FAILURE(errorCode)) { |
| + ures_close(&stackBundle); |
| + return; |
| + } |
| + } |
| + UResType expectedType = arraySink != NULL ? URES_ARRAY : URES_TABLE; |
| + if (ures_getType(rb) != expectedType) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + ures_close(&stackBundle); |
| + return; |
| + } |
| + // Get all table items with fallback. |
| + ResourceDataValue value; |
| + getAllContainerItemsWithFallback(rb, value, arraySink, tableSink, errorCode); |
| + ures_close(&stackBundle); |
| +} |
| + |
| +} // namespace |
| + |
| +U_CAPI void U_EXPORT2 |
| +ures_getAllArrayItemsWithFallback(const UResourceBundle *bundle, const char *path, |
| + ResourceArraySink &sink, UErrorCode &errorCode) { |
| + getAllContainerItemsWithFallback(bundle, path, &sink, NULL, errorCode); |
| +} |
| + |
| +U_CAPI void U_EXPORT2 |
| +ures_getAllTableItemsWithFallback(const UResourceBundle *bundle, const char *path, |
| + ResourceTableSink &sink, UErrorCode &errorCode) { |
| + getAllContainerItemsWithFallback(bundle, path, NULL, &sink, errorCode); |
| +} |
| |
| U_CAPI UResourceBundle* U_EXPORT2 ures_getByKey(const UResourceBundle *resB, const char* inKey, UResourceBundle *fillIn, UErrorCode *status) { |
| Resource res = RES_BOGUS; |
| diff --git a/source/common/uresdata.c b/source/common/uresdata.c |
| deleted file mode 100644 |
| index 53887fb..0000000 |
| --- a/source/common/uresdata.c |
| +++ /dev/null |
| @@ -1,1169 +0,0 @@ |
| -/* |
| -******************************************************************************* |
| -* Copyright (C) 1999-2015, International Business Machines Corporation |
| -* and others. All Rights Reserved. |
| -******************************************************************************* |
| -* file name: uresdata.c |
| -* encoding: US-ASCII |
| -* tab size: 8 (not used) |
| -* indentation:4 |
| -* |
| -* created on: 1999dec08 |
| -* created by: Markus W. Scherer |
| -* Modification History: |
| -* |
| -* Date Name Description |
| -* 06/20/2000 helena OS/400 port changes; mostly typecast. |
| -* 06/24/02 weiv Added support for resource sharing |
| -*/ |
| - |
| -#include "unicode/utypes.h" |
| -#include "unicode/udata.h" |
| -#include "unicode/ustring.h" |
| -#include "unicode/utf16.h" |
| -#include "cmemory.h" |
| -#include "cstring.h" |
| -#include "uarrsort.h" |
| -#include "udataswp.h" |
| -#include "ucol_swp.h" |
| -#include "uinvchar.h" |
| -#include "uresdata.h" |
| -#include "uresimp.h" |
| -#include "uassert.h" |
| - |
| -/* |
| - * Resource access helpers |
| - */ |
| - |
| -/* get a const char* pointer to the key with the keyOffset byte offset from pRoot */ |
| -#define RES_GET_KEY16(pResData, keyOffset) \ |
| - ((keyOffset)<(pResData)->localKeyLimit ? \ |
| - (const char *)(pResData)->pRoot+(keyOffset) : \ |
| - (pResData)->poolBundleKeys+(keyOffset)-(pResData)->localKeyLimit) |
| - |
| -#define RES_GET_KEY32(pResData, keyOffset) \ |
| - ((keyOffset)>=0 ? \ |
| - (const char *)(pResData)->pRoot+(keyOffset) : \ |
| - (pResData)->poolBundleKeys+((keyOffset)&0x7fffffff)) |
| - |
| -#define URESDATA_ITEM_NOT_FOUND -1 |
| - |
| -/* empty resources, returned when the resource offset is 0 */ |
| -static const uint16_t gEmpty16=0; |
| - |
| -static const struct { |
| - int32_t length; |
| - int32_t res; |
| -} gEmpty32={ 0, 0 }; |
| - |
| -static const struct { |
| - int32_t length; |
| - UChar nul; |
| - UChar pad; |
| -} gEmptyString={ 0, 0, 0 }; |
| - |
| -/* |
| - * All the type-access functions assume that |
| - * the resource is of the expected type. |
| - */ |
| - |
| -static int32_t |
| -_res_findTableItem(const ResourceData *pResData, const uint16_t *keyOffsets, int32_t length, |
| - const char *key, const char **realKey) { |
| - const char *tableKey; |
| - int32_t mid, start, limit; |
| - int result; |
| - |
| - /* do a binary search for the key */ |
| - start=0; |
| - limit=length; |
| - while(start<limit) { |
| - mid = (start + limit) / 2; |
| - tableKey = RES_GET_KEY16(pResData, keyOffsets[mid]); |
| - if (pResData->useNativeStrcmp) { |
| - result = uprv_strcmp(key, tableKey); |
| - } else { |
| - result = uprv_compareInvCharsAsAscii(key, tableKey); |
| - } |
| - if (result < 0) { |
| - limit = mid; |
| - } else if (result > 0) { |
| - start = mid + 1; |
| - } else { |
| - /* We found it! */ |
| - *realKey=tableKey; |
| - return mid; |
| - } |
| - } |
| - return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ |
| -} |
| - |
| -static int32_t |
| -_res_findTable32Item(const ResourceData *pResData, const int32_t *keyOffsets, int32_t length, |
| - const char *key, const char **realKey) { |
| - const char *tableKey; |
| - int32_t mid, start, limit; |
| - int result; |
| - |
| - /* do a binary search for the key */ |
| - start=0; |
| - limit=length; |
| - while(start<limit) { |
| - mid = (start + limit) / 2; |
| - tableKey = RES_GET_KEY32(pResData, keyOffsets[mid]); |
| - if (pResData->useNativeStrcmp) { |
| - result = uprv_strcmp(key, tableKey); |
| - } else { |
| - result = uprv_compareInvCharsAsAscii(key, tableKey); |
| - } |
| - if (result < 0) { |
| - limit = mid; |
| - } else if (result > 0) { |
| - start = mid + 1; |
| - } else { |
| - /* We found it! */ |
| - *realKey=tableKey; |
| - return mid; |
| - } |
| - } |
| - return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ |
| -} |
| - |
| -/* helper for res_load() ---------------------------------------------------- */ |
| - |
| -static UBool U_CALLCONV |
| -isAcceptable(void *context, |
| - const char *type, const char *name, |
| - const UDataInfo *pInfo) { |
| - uprv_memcpy(context, pInfo->formatVersion, 4); |
| - return (UBool)( |
| - pInfo->size>=20 && |
| - pInfo->isBigEndian==U_IS_BIG_ENDIAN && |
| - pInfo->charsetFamily==U_CHARSET_FAMILY && |
| - pInfo->sizeofUChar==U_SIZEOF_UCHAR && |
| - pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ |
| - pInfo->dataFormat[1]==0x65 && |
| - pInfo->dataFormat[2]==0x73 && |
| - pInfo->dataFormat[3]==0x42 && |
| - (1<=pInfo->formatVersion[0] && pInfo->formatVersion[0]<=3)); |
| -} |
| - |
| -/* semi-public functions ---------------------------------------------------- */ |
| - |
| -static void |
| -res_init(ResourceData *pResData, |
| - UVersionInfo formatVersion, const void *inBytes, int32_t length, |
| - UErrorCode *errorCode) { |
| - UResType rootType; |
| - |
| - /* get the root resource */ |
| - pResData->pRoot=(const int32_t *)inBytes; |
| - pResData->rootRes=(Resource)*pResData->pRoot; |
| - pResData->p16BitUnits=&gEmpty16; |
| - |
| - /* formatVersion 1.1 must have a root item and at least 5 indexes */ |
| - if(length>=0 && (length/4)<((formatVersion[0]==1 && formatVersion[1]==0) ? 1 : 1+5)) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - res_unload(pResData); |
| - return; |
| - } |
| - |
| - /* currently, we accept only resources that have a Table as their roots */ |
| - rootType=(UResType)RES_GET_TYPE(pResData->rootRes); |
| - if(!URES_IS_TABLE(rootType)) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - res_unload(pResData); |
| - return; |
| - } |
| - |
| - if(formatVersion[0]==1 && formatVersion[1]==0) { |
| - pResData->localKeyLimit=0x10000; /* greater than any 16-bit key string offset */ |
| - } else { |
| - /* bundles with formatVersion 1.1 and later contain an indexes[] array */ |
| - const int32_t *indexes=pResData->pRoot+1; |
| - int32_t indexLength=indexes[URES_INDEX_LENGTH]&0xff; |
| - if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - res_unload(pResData); |
| - return; |
| - } |
| - if( length>=0 && |
| - (length<((1+indexLength)<<2) || |
| - length<(indexes[URES_INDEX_BUNDLE_TOP]<<2)) |
| - ) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - res_unload(pResData); |
| - return; |
| - } |
| - if(indexes[URES_INDEX_KEYS_TOP]>(1+indexLength)) { |
| - pResData->localKeyLimit=indexes[URES_INDEX_KEYS_TOP]<<2; |
| - } |
| - if(formatVersion[0]>=3) { |
| - // In formatVersion 1, the indexLength took up this whole int. |
| - // In version 2, bits 31..8 were reserved and always 0. |
| - // In version 3, they contain bits 23..0 of the poolStringIndexLimit. |
| - // Bits 27..24 are in indexes[URES_INDEX_ATTRIBUTES] bits 15..12. |
| - pResData->poolStringIndexLimit=(int32_t)((uint32_t)indexes[URES_INDEX_LENGTH]>>8); |
| - } |
| - if(indexLength>URES_INDEX_ATTRIBUTES) { |
| - int32_t att=indexes[URES_INDEX_ATTRIBUTES]; |
| - pResData->noFallback=(UBool)(att&URES_ATT_NO_FALLBACK); |
| - pResData->isPoolBundle=(UBool)((att&URES_ATT_IS_POOL_BUNDLE)!=0); |
| - pResData->usesPoolBundle=(UBool)((att&URES_ATT_USES_POOL_BUNDLE)!=0); |
| - pResData->poolStringIndexLimit|=(att&0xf000)<<12; // bits 15..12 -> 27..24 |
| - pResData->poolStringIndex16Limit=(int32_t)((uint32_t)att>>16); |
| - } |
| - if((pResData->isPoolBundle || pResData->usesPoolBundle) && indexLength<=URES_INDEX_POOL_CHECKSUM) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - res_unload(pResData); |
| - return; |
| - } |
| - if( indexLength>URES_INDEX_16BIT_TOP && |
| - indexes[URES_INDEX_16BIT_TOP]>indexes[URES_INDEX_KEYS_TOP] |
| - ) { |
| - pResData->p16BitUnits=(const uint16_t *)(pResData->pRoot+indexes[URES_INDEX_KEYS_TOP]); |
| - } |
| - } |
| - |
| - if(formatVersion[0]==1 || U_CHARSET_FAMILY==U_ASCII_FAMILY) { |
| - /* |
| - * formatVersion 1: compare key strings in native-charset order |
| - * formatVersion 2 and up: compare key strings in ASCII order |
| - */ |
| - pResData->useNativeStrcmp=TRUE; |
| - } |
| -} |
| - |
| -U_CAPI void U_EXPORT2 |
| -res_read(ResourceData *pResData, |
| - const UDataInfo *pInfo, const void *inBytes, int32_t length, |
| - UErrorCode *errorCode) { |
| - UVersionInfo formatVersion; |
| - |
| - uprv_memset(pResData, 0, sizeof(ResourceData)); |
| - if(U_FAILURE(*errorCode)) { |
| - return; |
| - } |
| - if(!isAcceptable(formatVersion, NULL, NULL, pInfo)) { |
| - *errorCode=U_INVALID_FORMAT_ERROR; |
| - return; |
| - } |
| - res_init(pResData, formatVersion, inBytes, length, errorCode); |
| -} |
| - |
| -U_CFUNC void |
| -res_load(ResourceData *pResData, |
| - const char *path, const char *name, UErrorCode *errorCode) { |
| - UVersionInfo formatVersion; |
| - |
| - uprv_memset(pResData, 0, sizeof(ResourceData)); |
| - |
| - /* load the ResourceBundle file */ |
| - pResData->data=udata_openChoice(path, "res", name, isAcceptable, formatVersion, errorCode); |
| - if(U_FAILURE(*errorCode)) { |
| - return; |
| - } |
| - |
| - /* get its memory and initialize *pResData */ |
| - res_init(pResData, formatVersion, udata_getMemory(pResData->data), -1, errorCode); |
| -} |
| - |
| -U_CFUNC void |
| -res_unload(ResourceData *pResData) { |
| - if(pResData->data!=NULL) { |
| - udata_close(pResData->data); |
| - pResData->data=NULL; |
| - } |
| -} |
| - |
| -static const int8_t gPublicTypes[URES_LIMIT] = { |
| - URES_STRING, |
| - URES_BINARY, |
| - URES_TABLE, |
| - URES_ALIAS, |
| - |
| - URES_TABLE, /* URES_TABLE32 */ |
| - URES_TABLE, /* URES_TABLE16 */ |
| - URES_STRING, /* URES_STRING_V2 */ |
| - URES_INT, |
| - |
| - URES_ARRAY, |
| - URES_ARRAY, /* URES_ARRAY16 */ |
| - URES_NONE, |
| - URES_NONE, |
| - |
| - URES_NONE, |
| - URES_NONE, |
| - URES_INT_VECTOR, |
| - URES_NONE |
| -}; |
| - |
| -U_CAPI UResType U_EXPORT2 |
| -res_getPublicType(Resource res) { |
| - return (UResType)gPublicTypes[RES_GET_TYPE(res)]; |
| -} |
| - |
| -U_CAPI const UChar * U_EXPORT2 |
| -res_getString(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| - const UChar *p; |
| - uint32_t offset=RES_GET_OFFSET(res); |
| - int32_t length; |
| - if(RES_GET_TYPE(res)==URES_STRING_V2) { |
| - int32_t first; |
| - if(offset<pResData->poolStringIndexLimit) { |
| - p=(const UChar *)pResData->poolBundleStrings+offset; |
| - } else { |
| - p=(const UChar *)pResData->p16BitUnits+(offset-pResData->poolStringIndexLimit); |
| - } |
| - first=*p; |
| - if(!U16_IS_TRAIL(first)) { |
| - length=u_strlen(p); |
| - } else if(first<0xdfef) { |
| - length=first&0x3ff; |
| - ++p; |
| - } else if(first<0xdfff) { |
| - length=((first-0xdfef)<<16)|p[1]; |
| - p+=2; |
| - } else { |
| - length=((int32_t)p[1]<<16)|p[2]; |
| - p+=3; |
| - } |
| - } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ { |
| - const int32_t *p32= res==0 ? &gEmptyString.length : pResData->pRoot+res; |
| - length=*p32++; |
| - p=(const UChar *)p32; |
| - } else { |
| - p=NULL; |
| - length=0; |
| - } |
| - if(pLength) { |
| - *pLength=length; |
| - } |
| - return p; |
| -} |
| - |
| -U_CAPI const UChar * U_EXPORT2 |
| -res_getAlias(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| - const UChar *p; |
| - uint32_t offset=RES_GET_OFFSET(res); |
| - int32_t length; |
| - if(RES_GET_TYPE(res)==URES_ALIAS) { |
| - const int32_t *p32= offset==0 ? &gEmptyString.length : pResData->pRoot+offset; |
| - length=*p32++; |
| - p=(const UChar *)p32; |
| - } else { |
| - p=NULL; |
| - length=0; |
| - } |
| - if(pLength) { |
| - *pLength=length; |
| - } |
| - return p; |
| -} |
| - |
| -U_CAPI const uint8_t * U_EXPORT2 |
| -res_getBinary(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| - const uint8_t *p; |
| - uint32_t offset=RES_GET_OFFSET(res); |
| - int32_t length; |
| - if(RES_GET_TYPE(res)==URES_BINARY) { |
| - const int32_t *p32= offset==0 ? (const int32_t*)&gEmpty32 : pResData->pRoot+offset; |
| - length=*p32++; |
| - p=(const uint8_t *)p32; |
| - } else { |
| - p=NULL; |
| - length=0; |
| - } |
| - if(pLength) { |
| - *pLength=length; |
| - } |
| - return p; |
| -} |
| - |
| - |
| -U_CAPI const int32_t * U_EXPORT2 |
| -res_getIntVector(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| - const int32_t *p; |
| - uint32_t offset=RES_GET_OFFSET(res); |
| - int32_t length; |
| - if(RES_GET_TYPE(res)==URES_INT_VECTOR) { |
| - p= offset==0 ? (const int32_t *)&gEmpty32 : pResData->pRoot+offset; |
| - length=*p++; |
| - } else { |
| - p=NULL; |
| - length=0; |
| - } |
| - if(pLength) { |
| - *pLength=length; |
| - } |
| - return p; |
| -} |
| - |
| -U_CAPI int32_t U_EXPORT2 |
| -res_countArrayItems(const ResourceData *pResData, Resource res) { |
| - uint32_t offset=RES_GET_OFFSET(res); |
| - switch(RES_GET_TYPE(res)) { |
| - case URES_STRING: |
| - case URES_STRING_V2: |
| - case URES_BINARY: |
| - case URES_ALIAS: |
| - case URES_INT: |
| - case URES_INT_VECTOR: |
| - return 1; |
| - case URES_ARRAY: |
| - case URES_TABLE32: |
| - return offset==0 ? 0 : *(pResData->pRoot+offset); |
| - case URES_TABLE: |
| - return offset==0 ? 0 : *((const uint16_t *)(pResData->pRoot+offset)); |
| - case URES_ARRAY16: |
| - case URES_TABLE16: |
| - return pResData->p16BitUnits[offset]; |
| - default: |
| - return 0; |
| - } |
| -} |
| - |
| -static Resource |
| -makeResourceFrom16(const ResourceData *pResData, int32_t res16) { |
| - if(res16<pResData->poolStringIndex16Limit) { |
| - // Pool string, nothing to do. |
| - } else { |
| - // Local string, adjust the 16-bit offset to a regular one, |
| - // with a larger pool string index limit. |
| - res16=res16-pResData->poolStringIndex16Limit+pResData->poolStringIndexLimit; |
| - } |
| - return URES_MAKE_RESOURCE(URES_STRING_V2, res16); |
| -} |
| - |
| -U_CAPI Resource U_EXPORT2 |
| -res_getTableItemByKey(const ResourceData *pResData, Resource table, |
| - int32_t *indexR, const char **key) { |
| - uint32_t offset=RES_GET_OFFSET(table); |
| - int32_t length; |
| - int32_t idx; |
| - if(key == NULL || *key == NULL) { |
| - return RES_BOGUS; |
| - } |
| - switch(RES_GET_TYPE(table)) { |
| - case URES_TABLE: { |
| - if (offset!=0) { /* empty if offset==0 */ |
| - const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); |
| - length=*p++; |
| - *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); |
| - if(idx>=0) { |
| - const Resource *p32=(const Resource *)(p+length+(~length&1)); |
| - return p32[idx]; |
| - } |
| - } |
| - break; |
| - } |
| - case URES_TABLE16: { |
| - const uint16_t *p=pResData->p16BitUnits+offset; |
| - length=*p++; |
| - *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); |
| - if(idx>=0) { |
| - return makeResourceFrom16(pResData, p[length+idx]); |
| - } |
| - break; |
| - } |
| - case URES_TABLE32: { |
| - if (offset!=0) { /* empty if offset==0 */ |
| - const int32_t *p= pResData->pRoot+offset; |
| - length=*p++; |
| - *indexR=idx=_res_findTable32Item(pResData, p, length, *key, key); |
| - if(idx>=0) { |
| - return (Resource)p[length+idx]; |
| - } |
| - } |
| - break; |
| - } |
| - default: |
| - break; |
| - } |
| - return RES_BOGUS; |
| -} |
| - |
| -U_CAPI Resource U_EXPORT2 |
| -res_getTableItemByIndex(const ResourceData *pResData, Resource table, |
| - int32_t indexR, const char **key) { |
| - uint32_t offset=RES_GET_OFFSET(table); |
| - int32_t length; |
| - U_ASSERT(indexR>=0); /* to ensure the index is not negative */ |
| - switch(RES_GET_TYPE(table)) { |
| - case URES_TABLE: { |
| - if (offset != 0) { /* empty if offset==0 */ |
| - const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); |
| - length=*p++; |
| - if(indexR<length) { |
| - const Resource *p32=(const Resource *)(p+length+(~length&1)); |
| - if(key!=NULL) { |
| - *key=RES_GET_KEY16(pResData, p[indexR]); |
| - } |
| - return p32[indexR]; |
| - } |
| - } |
| - break; |
| - } |
| - case URES_TABLE16: { |
| - const uint16_t *p=pResData->p16BitUnits+offset; |
| - length=*p++; |
| - if(indexR<length) { |
| - if(key!=NULL) { |
| - *key=RES_GET_KEY16(pResData, p[indexR]); |
| - } |
| - return makeResourceFrom16(pResData, p[length+indexR]); |
| - } |
| - break; |
| - } |
| - case URES_TABLE32: { |
| - if (offset != 0) { /* empty if offset==0 */ |
| - const int32_t *p= pResData->pRoot+offset; |
| - length=*p++; |
| - if(indexR<length) { |
| - if(key!=NULL) { |
| - *key=RES_GET_KEY32(pResData, p[indexR]); |
| - } |
| - return (Resource)p[length+indexR]; |
| - } |
| - } |
| - break; |
| - } |
| - default: |
| - break; |
| - } |
| - return RES_BOGUS; |
| -} |
| - |
| -U_CAPI Resource U_EXPORT2 |
| -res_getResource(const ResourceData *pResData, const char *key) { |
| - const char *realKey=key; |
| - int32_t idx; |
| - return res_getTableItemByKey(pResData, pResData->rootRes, &idx, &realKey); |
| -} |
| - |
| -U_CAPI Resource U_EXPORT2 |
| -res_getArrayItem(const ResourceData *pResData, Resource array, int32_t indexR) { |
| - uint32_t offset=RES_GET_OFFSET(array); |
| - U_ASSERT(indexR>=0); /* to ensure the index is not negative */ |
| - switch(RES_GET_TYPE(array)) { |
| - case URES_ARRAY: { |
| - if (offset!=0) { /* empty if offset==0 */ |
| - const int32_t *p= pResData->pRoot+offset; |
| - if(indexR<*p) { |
| - return (Resource)p[1+indexR]; |
| - } |
| - } |
| - break; |
| - } |
| - case URES_ARRAY16: { |
| - const uint16_t *p=pResData->p16BitUnits+offset; |
| - if(indexR<*p) { |
| - return makeResourceFrom16(pResData, p[1+indexR]); |
| - } |
| - break; |
| - } |
| - default: |
| - break; |
| - } |
| - return RES_BOGUS; |
| -} |
| - |
| -U_CFUNC Resource |
| -res_findResource(const ResourceData *pResData, Resource r, char** path, const char** key) { |
| - /* we pass in a path. CollationElements/Sequence or zoneStrings/3/2 etc. |
| - * iterates over a path and stops when a scalar resource is found. This |
| - * CAN be an alias. Path gets set to the part that has not yet been processed. |
| - */ |
| - |
| - char *pathP = *path, *nextSepP = *path; |
| - char *closeIndex = NULL; |
| - Resource t1 = r; |
| - Resource t2; |
| - int32_t indexR = 0; |
| - UResType type = (UResType)RES_GET_TYPE(t1); |
| - |
| - /* if you come in with an empty path, you'll be getting back the same resource */ |
| - if(!uprv_strlen(pathP)) { |
| - return r; |
| - } |
| - |
| - /* one needs to have an aggregate resource in order to search in it */ |
| - if(!URES_IS_CONTAINER(type)) { |
| - return RES_BOGUS; |
| - } |
| - |
| - while(nextSepP && *pathP && t1 != RES_BOGUS && URES_IS_CONTAINER(type)) { |
| - /* Iteration stops if: the path has been consumed, we found a non-existing |
| - * resource (t1 == RES_BOGUS) or we found a scalar resource (including alias) |
| - */ |
| - nextSepP = uprv_strchr(pathP, RES_PATH_SEPARATOR); |
| - /* if there are more separators, terminate string |
| - * and set path to the remaining part of the string |
| - */ |
| - if(nextSepP != NULL) { |
| - *nextSepP = 0; /* overwrite the separator with a NUL to terminate the key */ |
| - *path = nextSepP+1; |
| - } else { |
| - *path = uprv_strchr(pathP, 0); |
| - } |
| - |
| - /* if the resource is a table */ |
| - /* try the key based access */ |
| - if(URES_IS_TABLE(type)) { |
| - *key = pathP; |
| - t2 = res_getTableItemByKey(pResData, t1, &indexR, key); |
| - if(t2 == RES_BOGUS) { |
| - /* if we fail to get the resource by key, maybe we got an index */ |
| - indexR = uprv_strtol(pathP, &closeIndex, 10); |
| - if(closeIndex != pathP) { |
| - /* if we indeed have an index, try to get the item by index */ |
| - t2 = res_getTableItemByIndex(pResData, t1, indexR, key); |
| - } |
| - } |
| - } else if(URES_IS_ARRAY(type)) { |
| - indexR = uprv_strtol(pathP, &closeIndex, 10); |
| - if(closeIndex != pathP) { |
| - t2 = res_getArrayItem(pResData, t1, indexR); |
| - } else { |
| - t2 = RES_BOGUS; /* have an array, but don't have a valid index */ |
| - } |
| - *key = NULL; |
| - } else { /* can't do much here, except setting t2 to bogus */ |
| - t2 = RES_BOGUS; |
| - } |
| - t1 = t2; |
| - type = (UResType)RES_GET_TYPE(t1); |
| - /* position pathP to next resource key/index */ |
| - pathP = *path; |
| - } |
| - |
| - return t1; |
| -} |
| - |
| -/* resource bundle swapping ------------------------------------------------- */ |
| - |
| -/* |
| - * Need to always enumerate the entire item tree, |
| - * track the lowest address of any item to use as the limit for char keys[], |
| - * track the highest address of any item to return the size of the data. |
| - * |
| - * We should have thought of storing those in the data... |
| - * It is possible to extend the data structure by putting additional values |
| - * in places that are inaccessible by ordinary enumeration of the item tree. |
| - * For example, additional integers could be stored at the beginning or |
| - * end of the key strings; this could be indicated by a minor version number, |
| - * and the data swapping would have to know about these values. |
| - * |
| - * The data structure does not forbid keys to be shared, so we must swap |
| - * all keys once instead of each key when it is referenced. |
| - * |
| - * These swapping functions assume that a resource bundle always has a length |
| - * that is a multiple of 4 bytes. |
| - * Currently, this is trivially true because genrb writes bundle tree leaves |
| - * physically first, before their branches, so that the root table with its |
| - * array of resource items (uint32_t values) is always last. |
| - */ |
| - |
| -/* definitions for table sorting ------------------------ */ |
| - |
| -/* |
| - * row of a temporary array |
| - * |
| - * gets platform-endian key string indexes and sorting indexes; |
| - * after sorting this array by keys, the actual key/value arrays are permutated |
| - * according to the sorting indexes |
| - */ |
| -typedef struct Row { |
| - int32_t keyIndex, sortIndex; |
| -} Row; |
| - |
| -static int32_t |
| -ures_compareRows(const void *context, const void *left, const void *right) { |
| - const char *keyChars=(const char *)context; |
| - return (int32_t)uprv_strcmp(keyChars+((const Row *)left)->keyIndex, |
| - keyChars+((const Row *)right)->keyIndex); |
| -} |
| - |
| -typedef struct TempTable { |
| - const char *keyChars; |
| - Row *rows; |
| - int32_t *resort; |
| - uint32_t *resFlags; |
| - int32_t localKeyLimit; |
| - uint8_t majorFormatVersion; |
| -} TempTable; |
| - |
| -enum { |
| - STACK_ROW_CAPACITY=200 |
| -}; |
| - |
| -/* The table item key string is not locally available. */ |
| -static const char *const gUnknownKey=""; |
| - |
| -/* resource table key for collation binaries: "%%CollationBin" */ |
| -static const UChar gCollationBinKey[]={ |
| - 0x25, 0x25, |
| - 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, |
| - 0x42, 0x69, 0x6e, |
| - 0 |
| -}; |
| - |
| -/* |
| - * swap one resource item |
| - */ |
| -static void |
| -ures_swapResource(const UDataSwapper *ds, |
| - const Resource *inBundle, Resource *outBundle, |
| - Resource res, /* caller swaps res itself */ |
| - const char *key, |
| - TempTable *pTempTable, |
| - UErrorCode *pErrorCode) { |
| - const Resource *p; |
| - Resource *q; |
| - int32_t offset, count; |
| - |
| - switch(RES_GET_TYPE(res)) { |
| - case URES_TABLE16: |
| - case URES_STRING_V2: |
| - case URES_INT: |
| - case URES_ARRAY16: |
| - /* integer, or points to 16-bit units, nothing to do here */ |
| - return; |
| - default: |
| - break; |
| - } |
| - |
| - /* all other types use an offset to point to their data */ |
| - offset=(int32_t)RES_GET_OFFSET(res); |
| - if(offset==0) { |
| - /* special offset indicating an empty item */ |
| - return; |
| - } |
| - if(pTempTable->resFlags[offset>>5]&((uint32_t)1<<(offset&0x1f))) { |
| - /* we already swapped this resource item */ |
| - return; |
| - } else { |
| - /* mark it as swapped now */ |
| - pTempTable->resFlags[offset>>5]|=((uint32_t)1<<(offset&0x1f)); |
| - } |
| - |
| - p=inBundle+offset; |
| - q=outBundle+offset; |
| - |
| - switch(RES_GET_TYPE(res)) { |
| - case URES_ALIAS: |
| - /* physically same value layout as string, fall through */ |
| - case URES_STRING: |
| - count=udata_readInt32(ds, (int32_t)*p); |
| - /* swap length */ |
| - ds->swapArray32(ds, p, 4, q, pErrorCode); |
| - /* swap each UChar (the terminating NUL would not change) */ |
| - ds->swapArray16(ds, p+1, 2*count, q+1, pErrorCode); |
| - break; |
| - case URES_BINARY: |
| - count=udata_readInt32(ds, (int32_t)*p); |
| - /* swap length */ |
| - ds->swapArray32(ds, p, 4, q, pErrorCode); |
| - /* no need to swap or copy bytes - ures_swap() copied them all */ |
| - |
| - /* swap known formats */ |
| -#if !UCONFIG_NO_COLLATION |
| - if( key!=NULL && /* the binary is in a table */ |
| - (key!=gUnknownKey ? |
| - /* its table key string is "%%CollationBin" */ |
| - 0==ds->compareInvChars(ds, key, -1, |
| - gCollationBinKey, UPRV_LENGTHOF(gCollationBinKey)-1) : |
| - /* its table key string is unknown but it looks like a collation binary */ |
| - ucol_looksLikeCollationBinary(ds, p+1, count)) |
| - ) { |
| - ucol_swap(ds, p+1, count, q+1, pErrorCode); |
| - } |
| -#endif |
| - break; |
| - case URES_TABLE: |
| - case URES_TABLE32: |
| - { |
| - const uint16_t *pKey16; |
| - uint16_t *qKey16; |
| - |
| - const int32_t *pKey32; |
| - int32_t *qKey32; |
| - |
| - Resource item; |
| - int32_t i, oldIndex; |
| - |
| - if(RES_GET_TYPE(res)==URES_TABLE) { |
| - /* get table item count */ |
| - pKey16=(const uint16_t *)p; |
| - qKey16=(uint16_t *)q; |
| - count=ds->readUInt16(*pKey16); |
| - |
| - pKey32=qKey32=NULL; |
| - |
| - /* swap count */ |
| - ds->swapArray16(ds, pKey16++, 2, qKey16++, pErrorCode); |
| - |
| - offset+=((1+count)+1)/2; |
| - } else { |
| - /* get table item count */ |
| - pKey32=(const int32_t *)p; |
| - qKey32=(int32_t *)q; |
| - count=udata_readInt32(ds, *pKey32); |
| - |
| - pKey16=qKey16=NULL; |
| - |
| - /* swap count */ |
| - ds->swapArray32(ds, pKey32++, 4, qKey32++, pErrorCode); |
| - |
| - offset+=1+count; |
| - } |
| - |
| - if(count==0) { |
| - break; |
| - } |
| - |
| - p=inBundle+offset; /* pointer to table resources */ |
| - q=outBundle+offset; |
| - |
| - /* recurse */ |
| - for(i=0; i<count; ++i) { |
| - const char *itemKey=gUnknownKey; |
| - if(pKey16!=NULL) { |
| - int32_t keyOffset=ds->readUInt16(pKey16[i]); |
| - if(keyOffset<pTempTable->localKeyLimit) { |
| - itemKey=(const char *)outBundle+keyOffset; |
| - } |
| - } else { |
| - int32_t keyOffset=udata_readInt32(ds, pKey32[i]); |
| - if(keyOffset>=0) { |
| - itemKey=(const char *)outBundle+keyOffset; |
| - } |
| - } |
| - item=ds->readUInt32(p[i]); |
| - ures_swapResource(ds, inBundle, outBundle, item, itemKey, pTempTable, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swapResource(table res=%08x)[%d].recurse(%08x) failed\n", |
| - res, i, item); |
| - return; |
| - } |
| - } |
| - |
| - if(pTempTable->majorFormatVersion>1 || ds->inCharset==ds->outCharset) { |
| - /* no need to sort, just swap the offset/value arrays */ |
| - if(pKey16!=NULL) { |
| - ds->swapArray16(ds, pKey16, count*2, qKey16, pErrorCode); |
| - ds->swapArray32(ds, p, count*4, q, pErrorCode); |
| - } else { |
| - /* swap key offsets and items as one array */ |
| - ds->swapArray32(ds, pKey32, count*2*4, qKey32, pErrorCode); |
| - } |
| - break; |
| - } |
| - |
| - /* |
| - * We need to sort tables by outCharset key strings because they |
| - * sort differently for different charset families. |
| - * ures_swap() already set pTempTable->keyChars appropriately. |
| - * First we set up a temporary table with the key indexes and |
| - * sorting indexes and sort that. |
| - * Then we permutate and copy/swap the actual values. |
| - */ |
| - if(pKey16!=NULL) { |
| - for(i=0; i<count; ++i) { |
| - pTempTable->rows[i].keyIndex=ds->readUInt16(pKey16[i]); |
| - pTempTable->rows[i].sortIndex=i; |
| - } |
| - } else { |
| - for(i=0; i<count; ++i) { |
| - pTempTable->rows[i].keyIndex=udata_readInt32(ds, pKey32[i]); |
| - pTempTable->rows[i].sortIndex=i; |
| - } |
| - } |
| - uprv_sortArray(pTempTable->rows, count, sizeof(Row), |
| - ures_compareRows, pTempTable->keyChars, |
| - FALSE, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swapResource(table res=%08x).uprv_sortArray(%d items) failed\n", |
| - res, count); |
| - return; |
| - } |
| - |
| - /* |
| - * copy/swap/permutate items |
| - * |
| - * If we swap in-place, then the permutation must use another |
| - * temporary array (pTempTable->resort) |
| - * before the results are copied to the outBundle. |
| - */ |
| - /* keys */ |
| - if(pKey16!=NULL) { |
| - uint16_t *rKey16; |
| - |
| - if(pKey16!=qKey16) { |
| - rKey16=qKey16; |
| - } else { |
| - rKey16=(uint16_t *)pTempTable->resort; |
| - } |
| - for(i=0; i<count; ++i) { |
| - oldIndex=pTempTable->rows[i].sortIndex; |
| - ds->swapArray16(ds, pKey16+oldIndex, 2, rKey16+i, pErrorCode); |
| - } |
| - if(qKey16!=rKey16) { |
| - uprv_memcpy(qKey16, rKey16, 2*count); |
| - } |
| - } else { |
| - int32_t *rKey32; |
| - |
| - if(pKey32!=qKey32) { |
| - rKey32=qKey32; |
| - } else { |
| - rKey32=pTempTable->resort; |
| - } |
| - for(i=0; i<count; ++i) { |
| - oldIndex=pTempTable->rows[i].sortIndex; |
| - ds->swapArray32(ds, pKey32+oldIndex, 4, rKey32+i, pErrorCode); |
| - } |
| - if(qKey32!=rKey32) { |
| - uprv_memcpy(qKey32, rKey32, 4*count); |
| - } |
| - } |
| - |
| - /* resources */ |
| - { |
| - Resource *r; |
| - |
| - |
| - if(p!=q) { |
| - r=q; |
| - } else { |
| - r=(Resource *)pTempTable->resort; |
| - } |
| - for(i=0; i<count; ++i) { |
| - oldIndex=pTempTable->rows[i].sortIndex; |
| - ds->swapArray32(ds, p+oldIndex, 4, r+i, pErrorCode); |
| - } |
| - if(q!=r) { |
| - uprv_memcpy(q, r, 4*count); |
| - } |
| - } |
| - } |
| - break; |
| - case URES_ARRAY: |
| - { |
| - Resource item; |
| - int32_t i; |
| - |
| - count=udata_readInt32(ds, (int32_t)*p); |
| - /* swap length */ |
| - ds->swapArray32(ds, p++, 4, q++, pErrorCode); |
| - |
| - /* recurse */ |
| - for(i=0; i<count; ++i) { |
| - item=ds->readUInt32(p[i]); |
| - ures_swapResource(ds, inBundle, outBundle, item, NULL, pTempTable, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swapResource(array res=%08x)[%d].recurse(%08x) failed\n", |
| - res, i, item); |
| - return; |
| - } |
| - } |
| - |
| - /* swap items */ |
| - ds->swapArray32(ds, p, 4*count, q, pErrorCode); |
| - } |
| - break; |
| - case URES_INT_VECTOR: |
| - count=udata_readInt32(ds, (int32_t)*p); |
| - /* swap length and each integer */ |
| - ds->swapArray32(ds, p, 4*(1+count), q, pErrorCode); |
| - break; |
| - default: |
| - /* also catches RES_BOGUS */ |
| - *pErrorCode=U_UNSUPPORTED_ERROR; |
| - break; |
| - } |
| -} |
| - |
| -U_CAPI int32_t U_EXPORT2 |
| -ures_swap(const UDataSwapper *ds, |
| - const void *inData, int32_t length, void *outData, |
| - UErrorCode *pErrorCode) { |
| - const UDataInfo *pInfo; |
| - const Resource *inBundle; |
| - Resource rootRes; |
| - int32_t headerSize, maxTableLength; |
| - |
| - Row rows[STACK_ROW_CAPACITY]; |
| - int32_t resort[STACK_ROW_CAPACITY]; |
| - TempTable tempTable; |
| - |
| - const int32_t *inIndexes; |
| - |
| - /* the following integers count Resource item offsets (4 bytes each), not bytes */ |
| - int32_t bundleLength, indexLength, keysBottom, keysTop, resBottom, top; |
| - |
| - /* udata_swapDataHeader checks the arguments */ |
| - headerSize=udata_swapDataHeader(ds, inData, length, outData, pErrorCode); |
| - if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { |
| - return 0; |
| - } |
| - |
| - /* check data format and format version */ |
| - pInfo=(const UDataInfo *)((const char *)inData+4); |
| - if(!( |
| - pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ |
| - pInfo->dataFormat[1]==0x65 && |
| - pInfo->dataFormat[2]==0x73 && |
| - pInfo->dataFormat[3]==0x42 && |
| - /* formatVersion 1.1+ or 2.x or 3.x */ |
| - ((pInfo->formatVersion[0]==1 && pInfo->formatVersion[1]>=1) || |
| - pInfo->formatVersion[0]==2 || pInfo->formatVersion[0]==3) |
| - )) { |
| - udata_printError(ds, "ures_swap(): data format %02x.%02x.%02x.%02x (format version %02x.%02x) is not a resource bundle\n", |
| - pInfo->dataFormat[0], pInfo->dataFormat[1], |
| - pInfo->dataFormat[2], pInfo->dataFormat[3], |
| - pInfo->formatVersion[0], pInfo->formatVersion[1]); |
| - *pErrorCode=U_UNSUPPORTED_ERROR; |
| - return 0; |
| - } |
| - tempTable.majorFormatVersion=pInfo->formatVersion[0]; |
| - |
| - /* a resource bundle must contain at least one resource item */ |
| - if(length<0) { |
| - bundleLength=-1; |
| - } else { |
| - bundleLength=(length-headerSize)/4; |
| - |
| - /* formatVersion 1.1 must have a root item and at least 5 indexes */ |
| - if(bundleLength<(1+5)) { |
| - udata_printError(ds, "ures_swap(): too few bytes (%d after header) for a resource bundle\n", |
| - length-headerSize); |
| - *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| - return 0; |
| - } |
| - } |
| - |
| - inBundle=(const Resource *)((const char *)inData+headerSize); |
| - rootRes=ds->readUInt32(*inBundle); |
| - |
| - /* formatVersion 1.1 adds the indexes[] array */ |
| - inIndexes=(const int32_t *)(inBundle+1); |
| - |
| - indexLength=udata_readInt32(ds, inIndexes[URES_INDEX_LENGTH])&0xff; |
| - if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { |
| - udata_printError(ds, "ures_swap(): too few indexes for a 1.1+ resource bundle\n"); |
| - *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| - return 0; |
| - } |
| - keysBottom=1+indexLength; |
| - keysTop=udata_readInt32(ds, inIndexes[URES_INDEX_KEYS_TOP]); |
| - if(indexLength>URES_INDEX_16BIT_TOP) { |
| - resBottom=udata_readInt32(ds, inIndexes[URES_INDEX_16BIT_TOP]); |
| - } else { |
| - resBottom=keysTop; |
| - } |
| - top=udata_readInt32(ds, inIndexes[URES_INDEX_BUNDLE_TOP]); |
| - maxTableLength=udata_readInt32(ds, inIndexes[URES_INDEX_MAX_TABLE_LENGTH]); |
| - |
| - if(0<=bundleLength && bundleLength<top) { |
| - udata_printError(ds, "ures_swap(): resource top %d exceeds bundle length %d\n", |
| - top, bundleLength); |
| - *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| - return 0; |
| - } |
| - if(keysTop>(1+indexLength)) { |
| - tempTable.localKeyLimit=keysTop<<2; |
| - } else { |
| - tempTable.localKeyLimit=0; |
| - } |
| - |
| - if(length>=0) { |
| - Resource *outBundle=(Resource *)((char *)outData+headerSize); |
| - |
| - /* track which resources we have already swapped */ |
| - uint32_t stackResFlags[STACK_ROW_CAPACITY]; |
| - int32_t resFlagsLength; |
| - |
| - /* |
| - * We need one bit per 4 resource bundle bytes so that we can track |
| - * every possible Resource for whether we have swapped it already. |
| - * Multiple Resource words can refer to the same bundle offsets |
| - * for sharing identical values. |
| - * We could optimize this by allocating only for locations above |
| - * where Resource values are stored (above keys & strings). |
| - */ |
| - resFlagsLength=(length+31)>>5; /* number of bytes needed */ |
| - resFlagsLength=(resFlagsLength+3)&~3; /* multiple of 4 bytes for uint32_t */ |
| - if(resFlagsLength<=sizeof(stackResFlags)) { |
| - tempTable.resFlags=stackResFlags; |
| - } else { |
| - tempTable.resFlags=(uint32_t *)uprv_malloc(resFlagsLength); |
| - if(tempTable.resFlags==NULL) { |
| - udata_printError(ds, "ures_swap(): unable to allocate memory for tracking resources\n"); |
| - *pErrorCode=U_MEMORY_ALLOCATION_ERROR; |
| - return 0; |
| - } |
| - } |
| - uprv_memset(tempTable.resFlags, 0, resFlagsLength); |
| - |
| - /* copy the bundle for binary and inaccessible data */ |
| - if(inData!=outData) { |
| - uprv_memcpy(outBundle, inBundle, 4*top); |
| - } |
| - |
| - /* swap the key strings, but not the padding bytes (0xaa) after the last string and its NUL */ |
| - udata_swapInvStringBlock(ds, inBundle+keysBottom, 4*(keysTop-keysBottom), |
| - outBundle+keysBottom, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swap().udata_swapInvStringBlock(keys[%d]) failed\n", 4*(keysTop-keysBottom)); |
| - return 0; |
| - } |
| - |
| - /* swap the 16-bit units (strings, table16, array16) */ |
| - if(keysTop<resBottom) { |
| - ds->swapArray16(ds, inBundle+keysTop, (resBottom-keysTop)*4, outBundle+keysTop, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swap().swapArray16(16-bit units[%d]) failed\n", 2*(resBottom-keysTop)); |
| - return 0; |
| - } |
| - } |
| - |
| - /* allocate the temporary table for sorting resource tables */ |
| - tempTable.keyChars=(const char *)outBundle; /* sort by outCharset */ |
| - if(tempTable.majorFormatVersion>1 || maxTableLength<=STACK_ROW_CAPACITY) { |
| - tempTable.rows=rows; |
| - tempTable.resort=resort; |
| - } else { |
| - tempTable.rows=(Row *)uprv_malloc(maxTableLength*sizeof(Row)+maxTableLength*4); |
| - if(tempTable.rows==NULL) { |
| - udata_printError(ds, "ures_swap(): unable to allocate memory for sorting tables (max length: %d)\n", |
| - maxTableLength); |
| - *pErrorCode=U_MEMORY_ALLOCATION_ERROR; |
| - if(tempTable.resFlags!=stackResFlags) { |
| - uprv_free(tempTable.resFlags); |
| - } |
| - return 0; |
| - } |
| - tempTable.resort=(int32_t *)(tempTable.rows+maxTableLength); |
| - } |
| - |
| - /* swap the resources */ |
| - ures_swapResource(ds, inBundle, outBundle, rootRes, NULL, &tempTable, pErrorCode); |
| - if(U_FAILURE(*pErrorCode)) { |
| - udata_printError(ds, "ures_swapResource(root res=%08x) failed\n", |
| - rootRes); |
| - } |
| - |
| - if(tempTable.rows!=rows) { |
| - uprv_free(tempTable.rows); |
| - } |
| - if(tempTable.resFlags!=stackResFlags) { |
| - uprv_free(tempTable.resFlags); |
| - } |
| - |
| - /* swap the root resource and indexes */ |
| - ds->swapArray32(ds, inBundle, keysBottom*4, outBundle, pErrorCode); |
| - } |
| - |
| - return headerSize+4*top; |
| -} |
| diff --git a/source/common/uresdata.cpp b/source/common/uresdata.cpp |
| new file mode 100644 |
| index 0000000..fbfb56d |
| --- /dev/null |
| +++ b/source/common/uresdata.cpp |
| @@ -0,0 +1,1466 @@ |
| +/* |
| +******************************************************************************* |
| +* Copyright (C) 1999-2015, International Business Machines Corporation |
| +* and others. All Rights Reserved. |
| +******************************************************************************* |
| +* file name: uresdata.cpp |
| +* encoding: US-ASCII |
| +* tab size: 8 (not used) |
| +* indentation:4 |
| +* |
| +* created on: 1999dec08 |
| +* created by: Markus W. Scherer |
| +* Modification History: |
| +* |
| +* Date Name Description |
| +* 06/20/2000 helena OS/400 port changes; mostly typecast. |
| +* 06/24/02 weiv Added support for resource sharing |
| +*/ |
| + |
| +#include "unicode/utypes.h" |
| +#include "unicode/udata.h" |
| +#include "unicode/ustring.h" |
| +#include "unicode/utf16.h" |
| +#include "cmemory.h" |
| +#include "cstring.h" |
| +#include "resource.h" |
| +#include "uarrsort.h" |
| +#include "uassert.h" |
| +#include "ucol_swp.h" |
| +#include "udataswp.h" |
| +#include "uinvchar.h" |
| +#include "uresdata.h" |
| +#include "uresimp.h" |
| + |
| +/* |
| + * Resource access helpers |
| + */ |
| + |
| +/* get a const char* pointer to the key with the keyOffset byte offset from pRoot */ |
| +#define RES_GET_KEY16(pResData, keyOffset) \ |
| + ((keyOffset)<(pResData)->localKeyLimit ? \ |
| + (const char *)(pResData)->pRoot+(keyOffset) : \ |
| + (pResData)->poolBundleKeys+(keyOffset)-(pResData)->localKeyLimit) |
| + |
| +#define RES_GET_KEY32(pResData, keyOffset) \ |
| + ((keyOffset)>=0 ? \ |
| + (const char *)(pResData)->pRoot+(keyOffset) : \ |
| + (pResData)->poolBundleKeys+((keyOffset)&0x7fffffff)) |
| + |
| +#define URESDATA_ITEM_NOT_FOUND -1 |
| + |
| +/* empty resources, returned when the resource offset is 0 */ |
| +static const uint16_t gEmpty16=0; |
| + |
| +static const struct { |
| + int32_t length; |
| + int32_t res; |
| +} gEmpty32={ 0, 0 }; |
| + |
| +static const struct { |
| + int32_t length; |
| + UChar nul; |
| + UChar pad; |
| +} gEmptyString={ 0, 0, 0 }; |
| + |
| +/* |
| + * All the type-access functions assume that |
| + * the resource is of the expected type. |
| + */ |
| + |
| +static int32_t |
| +_res_findTableItem(const ResourceData *pResData, const uint16_t *keyOffsets, int32_t length, |
| + const char *key, const char **realKey) { |
| + const char *tableKey; |
| + int32_t mid, start, limit; |
| + int result; |
| + |
| + /* do a binary search for the key */ |
| + start=0; |
| + limit=length; |
| + while(start<limit) { |
| + mid = (start + limit) / 2; |
| + tableKey = RES_GET_KEY16(pResData, keyOffsets[mid]); |
| + if (pResData->useNativeStrcmp) { |
| + result = uprv_strcmp(key, tableKey); |
| + } else { |
| + result = uprv_compareInvCharsAsAscii(key, tableKey); |
| + } |
| + if (result < 0) { |
| + limit = mid; |
| + } else if (result > 0) { |
| + start = mid + 1; |
| + } else { |
| + /* We found it! */ |
| + *realKey=tableKey; |
| + return mid; |
| + } |
| + } |
| + return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ |
| +} |
| + |
| +static int32_t |
| +_res_findTable32Item(const ResourceData *pResData, const int32_t *keyOffsets, int32_t length, |
| + const char *key, const char **realKey) { |
| + const char *tableKey; |
| + int32_t mid, start, limit; |
| + int result; |
| + |
| + /* do a binary search for the key */ |
| + start=0; |
| + limit=length; |
| + while(start<limit) { |
| + mid = (start + limit) / 2; |
| + tableKey = RES_GET_KEY32(pResData, keyOffsets[mid]); |
| + if (pResData->useNativeStrcmp) { |
| + result = uprv_strcmp(key, tableKey); |
| + } else { |
| + result = uprv_compareInvCharsAsAscii(key, tableKey); |
| + } |
| + if (result < 0) { |
| + limit = mid; |
| + } else if (result > 0) { |
| + start = mid + 1; |
| + } else { |
| + /* We found it! */ |
| + *realKey=tableKey; |
| + return mid; |
| + } |
| + } |
| + return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ |
| +} |
| + |
| +/* helper for res_load() ---------------------------------------------------- */ |
| + |
| +static UBool U_CALLCONV |
| +isAcceptable(void *context, |
| + const char * /*type*/, const char * /*name*/, |
| + const UDataInfo *pInfo) { |
| + uprv_memcpy(context, pInfo->formatVersion, 4); |
| + return (UBool)( |
| + pInfo->size>=20 && |
| + pInfo->isBigEndian==U_IS_BIG_ENDIAN && |
| + pInfo->charsetFamily==U_CHARSET_FAMILY && |
| + pInfo->sizeofUChar==U_SIZEOF_UCHAR && |
| + pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ |
| + pInfo->dataFormat[1]==0x65 && |
| + pInfo->dataFormat[2]==0x73 && |
| + pInfo->dataFormat[3]==0x42 && |
| + (1<=pInfo->formatVersion[0] && pInfo->formatVersion[0]<=3)); |
| +} |
| + |
| +/* semi-public functions ---------------------------------------------------- */ |
| + |
| +static void |
| +res_init(ResourceData *pResData, |
| + UVersionInfo formatVersion, const void *inBytes, int32_t length, |
| + UErrorCode *errorCode) { |
| + UResType rootType; |
| + |
| + /* get the root resource */ |
| + pResData->pRoot=(const int32_t *)inBytes; |
| + pResData->rootRes=(Resource)*pResData->pRoot; |
| + pResData->p16BitUnits=&gEmpty16; |
| + |
| + /* formatVersion 1.1 must have a root item and at least 5 indexes */ |
| + if(length>=0 && (length/4)<((formatVersion[0]==1 && formatVersion[1]==0) ? 1 : 1+5)) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + res_unload(pResData); |
| + return; |
| + } |
| + |
| + /* currently, we accept only resources that have a Table as their roots */ |
| + rootType=(UResType)RES_GET_TYPE(pResData->rootRes); |
| + if(!URES_IS_TABLE(rootType)) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + res_unload(pResData); |
| + return; |
| + } |
| + |
| + if(formatVersion[0]==1 && formatVersion[1]==0) { |
| + pResData->localKeyLimit=0x10000; /* greater than any 16-bit key string offset */ |
| + } else { |
| + /* bundles with formatVersion 1.1 and later contain an indexes[] array */ |
| + const int32_t *indexes=pResData->pRoot+1; |
| + int32_t indexLength=indexes[URES_INDEX_LENGTH]&0xff; |
| + if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + res_unload(pResData); |
| + return; |
| + } |
| + if( length>=0 && |
| + (length<((1+indexLength)<<2) || |
| + length<(indexes[URES_INDEX_BUNDLE_TOP]<<2)) |
| + ) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + res_unload(pResData); |
| + return; |
| + } |
| + if(indexes[URES_INDEX_KEYS_TOP]>(1+indexLength)) { |
| + pResData->localKeyLimit=indexes[URES_INDEX_KEYS_TOP]<<2; |
| + } |
| + if(formatVersion[0]>=3) { |
| + // In formatVersion 1, the indexLength took up this whole int. |
| + // In version 2, bits 31..8 were reserved and always 0. |
| + // In version 3, they contain bits 23..0 of the poolStringIndexLimit. |
| + // Bits 27..24 are in indexes[URES_INDEX_ATTRIBUTES] bits 15..12. |
| + pResData->poolStringIndexLimit=(int32_t)((uint32_t)indexes[URES_INDEX_LENGTH]>>8); |
| + } |
| + if(indexLength>URES_INDEX_ATTRIBUTES) { |
| + int32_t att=indexes[URES_INDEX_ATTRIBUTES]; |
| + pResData->noFallback=(UBool)(att&URES_ATT_NO_FALLBACK); |
| + pResData->isPoolBundle=(UBool)((att&URES_ATT_IS_POOL_BUNDLE)!=0); |
| + pResData->usesPoolBundle=(UBool)((att&URES_ATT_USES_POOL_BUNDLE)!=0); |
| + pResData->poolStringIndexLimit|=(att&0xf000)<<12; // bits 15..12 -> 27..24 |
| + pResData->poolStringIndex16Limit=(int32_t)((uint32_t)att>>16); |
| + } |
| + if((pResData->isPoolBundle || pResData->usesPoolBundle) && indexLength<=URES_INDEX_POOL_CHECKSUM) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + res_unload(pResData); |
| + return; |
| + } |
| + if( indexLength>URES_INDEX_16BIT_TOP && |
| + indexes[URES_INDEX_16BIT_TOP]>indexes[URES_INDEX_KEYS_TOP] |
| + ) { |
| + pResData->p16BitUnits=(const uint16_t *)(pResData->pRoot+indexes[URES_INDEX_KEYS_TOP]); |
| + } |
| + } |
| + |
| + if(formatVersion[0]==1 || U_CHARSET_FAMILY==U_ASCII_FAMILY) { |
| + /* |
| + * formatVersion 1: compare key strings in native-charset order |
| + * formatVersion 2 and up: compare key strings in ASCII order |
| + */ |
| + pResData->useNativeStrcmp=TRUE; |
| + } |
| +} |
| + |
| +U_CAPI void U_EXPORT2 |
| +res_read(ResourceData *pResData, |
| + const UDataInfo *pInfo, const void *inBytes, int32_t length, |
| + UErrorCode *errorCode) { |
| + UVersionInfo formatVersion; |
| + |
| + uprv_memset(pResData, 0, sizeof(ResourceData)); |
| + if(U_FAILURE(*errorCode)) { |
| + return; |
| + } |
| + if(!isAcceptable(formatVersion, NULL, NULL, pInfo)) { |
| + *errorCode=U_INVALID_FORMAT_ERROR; |
| + return; |
| + } |
| + res_init(pResData, formatVersion, inBytes, length, errorCode); |
| +} |
| + |
| +U_CFUNC void |
| +res_load(ResourceData *pResData, |
| + const char *path, const char *name, UErrorCode *errorCode) { |
| + UVersionInfo formatVersion; |
| + |
| + uprv_memset(pResData, 0, sizeof(ResourceData)); |
| + |
| + /* load the ResourceBundle file */ |
| + pResData->data=udata_openChoice(path, "res", name, isAcceptable, formatVersion, errorCode); |
| + if(U_FAILURE(*errorCode)) { |
| + return; |
| + } |
| + |
| + /* get its memory and initialize *pResData */ |
| + res_init(pResData, formatVersion, udata_getMemory(pResData->data), -1, errorCode); |
| +} |
| + |
| +U_CFUNC void |
| +res_unload(ResourceData *pResData) { |
| + if(pResData->data!=NULL) { |
| + udata_close(pResData->data); |
| + pResData->data=NULL; |
| + } |
| +} |
| + |
| +static const int8_t gPublicTypes[URES_LIMIT] = { |
| + URES_STRING, |
| + URES_BINARY, |
| + URES_TABLE, |
| + URES_ALIAS, |
| + |
| + URES_TABLE, /* URES_TABLE32 */ |
| + URES_TABLE, /* URES_TABLE16 */ |
| + URES_STRING, /* URES_STRING_V2 */ |
| + URES_INT, |
| + |
| + URES_ARRAY, |
| + URES_ARRAY, /* URES_ARRAY16 */ |
| + URES_NONE, |
| + URES_NONE, |
| + |
| + URES_NONE, |
| + URES_NONE, |
| + URES_INT_VECTOR, |
| + URES_NONE |
| +}; |
| + |
| +U_CAPI UResType U_EXPORT2 |
| +res_getPublicType(Resource res) { |
| + return (UResType)gPublicTypes[RES_GET_TYPE(res)]; |
| +} |
| + |
| +U_CAPI const UChar * U_EXPORT2 |
| +res_getString(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| + const UChar *p; |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + int32_t length; |
| + if(RES_GET_TYPE(res)==URES_STRING_V2) { |
| + int32_t first; |
| + if((int32_t)offset<pResData->poolStringIndexLimit) { |
| + p=(const UChar *)pResData->poolBundleStrings+offset; |
| + } else { |
| + p=(const UChar *)pResData->p16BitUnits+(offset-pResData->poolStringIndexLimit); |
| + } |
| + first=*p; |
| + if(!U16_IS_TRAIL(first)) { |
| + length=u_strlen(p); |
| + } else if(first<0xdfef) { |
| + length=first&0x3ff; |
| + ++p; |
| + } else if(first<0xdfff) { |
| + length=((first-0xdfef)<<16)|p[1]; |
| + p+=2; |
| + } else { |
| + length=((int32_t)p[1]<<16)|p[2]; |
| + p+=3; |
| + } |
| + } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ { |
| + const int32_t *p32= res==0 ? &gEmptyString.length : pResData->pRoot+res; |
| + length=*p32++; |
| + p=(const UChar *)p32; |
| + } else { |
| + p=NULL; |
| + length=0; |
| + } |
| + if(pLength) { |
| + *pLength=length; |
| + } |
| + return p; |
| +} |
| + |
| +namespace { |
| + |
| +/** |
| + * CLDR string value (three empty-set symbols)=={2205, 2205, 2205} |
| + * prevents fallback to the parent bundle. |
| + * TODO: combine with other code that handles this marker, use EMPTY_SET constant. |
| + * TODO: maybe move to uresbund.cpp? |
| + */ |
| +UBool isNoInheritanceMarker(const ResourceData *pResData, Resource res) { |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + if (offset == 0) { |
| + // empty string |
| + } else if (res == offset) { |
| + const int32_t *p32=pResData->pRoot+res; |
| + int32_t length=*p32; |
| + const UChar *p=(const UChar *)p32; |
| + return length == 3 && p[2] == 0x2205 && p[3] == 0x2205 && p[4] == 0x2205; |
| + } else if (RES_GET_TYPE(res) == URES_STRING_V2) { |
| + const UChar *p; |
| + if((int32_t)offset<pResData->poolStringIndexLimit) { |
| + p=(const UChar *)pResData->poolBundleStrings+offset; |
| + } else { |
| + p=(const UChar *)pResData->p16BitUnits+(offset-pResData->poolStringIndexLimit); |
| + } |
| + int32_t first=*p; |
| + if (first == 0x2205) { // implicit length |
| + return p[1] == 0x2205 && p[2] == 0x2205 && p[3] == 0; |
| + } else if (first == 0xdc03) { // explicit length 3 (should not occur) |
| + return p[1] == 0x2205 && p[2] == 0x2205 && p[3] == 0x2205; |
| + } else { |
| + // Assume that the string has not been stored with more length units than necessary. |
| + return FALSE; |
| + } |
| + } |
| + return FALSE; |
| +} |
| + |
| +} // namespace |
| + |
| +U_CAPI const UChar * U_EXPORT2 |
| +res_getAlias(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| + const UChar *p; |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + int32_t length; |
| + if(RES_GET_TYPE(res)==URES_ALIAS) { |
| + const int32_t *p32= offset==0 ? &gEmptyString.length : pResData->pRoot+offset; |
| + length=*p32++; |
| + p=(const UChar *)p32; |
| + } else { |
| + p=NULL; |
| + length=0; |
| + } |
| + if(pLength) { |
| + *pLength=length; |
| + } |
| + return p; |
| +} |
| + |
| +U_CAPI const uint8_t * U_EXPORT2 |
| +res_getBinary(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| + const uint8_t *p; |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + int32_t length; |
| + if(RES_GET_TYPE(res)==URES_BINARY) { |
| + const int32_t *p32= offset==0 ? (const int32_t*)&gEmpty32 : pResData->pRoot+offset; |
| + length=*p32++; |
| + p=(const uint8_t *)p32; |
| + } else { |
| + p=NULL; |
| + length=0; |
| + } |
| + if(pLength) { |
| + *pLength=length; |
| + } |
| + return p; |
| +} |
| + |
| + |
| +U_CAPI const int32_t * U_EXPORT2 |
| +res_getIntVector(const ResourceData *pResData, Resource res, int32_t *pLength) { |
| + const int32_t *p; |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + int32_t length; |
| + if(RES_GET_TYPE(res)==URES_INT_VECTOR) { |
| + p= offset==0 ? (const int32_t *)&gEmpty32 : pResData->pRoot+offset; |
| + length=*p++; |
| + } else { |
| + p=NULL; |
| + length=0; |
| + } |
| + if(pLength) { |
| + *pLength=length; |
| + } |
| + return p; |
| +} |
| + |
| +U_CAPI int32_t U_EXPORT2 |
| +res_countArrayItems(const ResourceData *pResData, Resource res) { |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + switch(RES_GET_TYPE(res)) { |
| + case URES_STRING: |
| + case URES_STRING_V2: |
| + case URES_BINARY: |
| + case URES_ALIAS: |
| + case URES_INT: |
| + case URES_INT_VECTOR: |
| + return 1; |
| + case URES_ARRAY: |
| + case URES_TABLE32: |
| + return offset==0 ? 0 : *(pResData->pRoot+offset); |
| + case URES_TABLE: |
| + return offset==0 ? 0 : *((const uint16_t *)(pResData->pRoot+offset)); |
| + case URES_ARRAY16: |
| + case URES_TABLE16: |
| + return pResData->p16BitUnits[offset]; |
| + default: |
| + return 0; |
| + } |
| +} |
| + |
| +namespace { |
| + |
| +int32_t getArrayLength(const ResourceData *pResData, Resource res) { |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + if(offset == 0) { |
| + return 0; |
| + } |
| + int32_t type = RES_GET_TYPE(res); |
| + if(type == URES_ARRAY) { |
| + return *(pResData->pRoot+offset); |
| + } else if(type == URES_ARRAY16) { |
| + return pResData->p16BitUnits[offset]; |
| + } else { |
| + return 0; |
| + } |
| +} |
| + |
| +int32_t getTableLength(const ResourceData *pResData, Resource res) { |
| + uint32_t offset=RES_GET_OFFSET(res); |
| + if(offset == 0) { |
| + return 0; |
| + } |
| + int32_t type = RES_GET_TYPE(res); |
| + if(type == URES_TABLE) { |
| + return *((const uint16_t *)(pResData->pRoot+offset)); |
| + } else if(type == URES_TABLE16) { |
| + return pResData->p16BitUnits[offset]; |
| + } else if(type == URES_TABLE32) { |
| + return *(pResData->pRoot+offset); |
| + } else { |
| + return 0; |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +ResourceDataValue::~ResourceDataValue() {} |
| + |
| +UResType ResourceDataValue::getType() const { |
| + return res_getPublicType(res); |
| +} |
| + |
| +const UChar *ResourceDataValue::getString(int32_t &length, UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return NULL; |
| + } |
| + const UChar *s = res_getString(pResData, res, &length); |
| + if(s == NULL) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return s; |
| +} |
| + |
| +const UChar *ResourceDataValue::getAliasString(int32_t &length, UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return NULL; |
| + } |
| + const UChar *s = res_getAlias(pResData, res, &length); |
| + if(s == NULL) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return s; |
| +} |
| + |
| +int32_t ResourceDataValue::getInt(UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return 0; |
| + } |
| + if(RES_GET_TYPE(res) != URES_INT) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return RES_GET_INT(res); |
| +} |
| + |
| +uint32_t ResourceDataValue::getUInt(UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return 0; |
| + } |
| + if(RES_GET_TYPE(res) != URES_INT) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return RES_GET_UINT(res); |
| +} |
| + |
| +const int32_t *ResourceDataValue::getIntVector(int32_t &length, UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return NULL; |
| + } |
| + const int32_t *iv = res_getIntVector(pResData, res, &length); |
| + if(iv == NULL) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return iv; |
| +} |
| + |
| +const uint8_t *ResourceDataValue::getBinary(int32_t &length, UErrorCode &errorCode) const { |
| + if(U_FAILURE(errorCode)) { |
| + return NULL; |
| + } |
| + const uint8_t *b = res_getBinary(pResData, res, &length); |
| + if(b == NULL) { |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + } |
| + return b; |
| +} |
| + |
| +U_NAMESPACE_END |
| + |
| +static Resource |
| +makeResourceFrom16(const ResourceData *pResData, int32_t res16) { |
| + if(res16<pResData->poolStringIndex16Limit) { |
| + // Pool string, nothing to do. |
| + } else { |
| + // Local string, adjust the 16-bit offset to a regular one, |
| + // with a larger pool string index limit. |
| + res16=res16-pResData->poolStringIndex16Limit+pResData->poolStringIndexLimit; |
| + } |
| + return URES_MAKE_RESOURCE(URES_STRING_V2, res16); |
| +} |
| + |
| +U_CAPI Resource U_EXPORT2 |
| +res_getTableItemByKey(const ResourceData *pResData, Resource table, |
| + int32_t *indexR, const char **key) { |
| + uint32_t offset=RES_GET_OFFSET(table); |
| + int32_t length; |
| + int32_t idx; |
| + if(key == NULL || *key == NULL) { |
| + return RES_BOGUS; |
| + } |
| + switch(RES_GET_TYPE(table)) { |
| + case URES_TABLE: { |
| + if (offset!=0) { /* empty if offset==0 */ |
| + const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); |
| + length=*p++; |
| + *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); |
| + if(idx>=0) { |
| + const Resource *p32=(const Resource *)(p+length+(~length&1)); |
| + return p32[idx]; |
| + } |
| + } |
| + break; |
| + } |
| + case URES_TABLE16: { |
| + const uint16_t *p=pResData->p16BitUnits+offset; |
| + length=*p++; |
| + *indexR=idx=_res_findTableItem(pResData, p, length, *key, key); |
| + if(idx>=0) { |
| + return makeResourceFrom16(pResData, p[length+idx]); |
| + } |
| + break; |
| + } |
| + case URES_TABLE32: { |
| + if (offset!=0) { /* empty if offset==0 */ |
| + const int32_t *p= pResData->pRoot+offset; |
| + length=*p++; |
| + *indexR=idx=_res_findTable32Item(pResData, p, length, *key, key); |
| + if(idx>=0) { |
| + return (Resource)p[length+idx]; |
| + } |
| + } |
| + break; |
| + } |
| + default: |
| + break; |
| + } |
| + return RES_BOGUS; |
| +} |
| + |
| +U_CAPI Resource U_EXPORT2 |
| +res_getTableItemByIndex(const ResourceData *pResData, Resource table, |
| + int32_t indexR, const char **key) { |
| + uint32_t offset=RES_GET_OFFSET(table); |
| + int32_t length; |
| + U_ASSERT(indexR>=0); /* to ensure the index is not negative */ |
| + switch(RES_GET_TYPE(table)) { |
| + case URES_TABLE: { |
| + if (offset != 0) { /* empty if offset==0 */ |
| + const uint16_t *p= (const uint16_t *)(pResData->pRoot+offset); |
| + length=*p++; |
| + if(indexR<length) { |
| + const Resource *p32=(const Resource *)(p+length+(~length&1)); |
| + if(key!=NULL) { |
| + *key=RES_GET_KEY16(pResData, p[indexR]); |
| + } |
| + return p32[indexR]; |
| + } |
| + } |
| + break; |
| + } |
| + case URES_TABLE16: { |
| + const uint16_t *p=pResData->p16BitUnits+offset; |
| + length=*p++; |
| + if(indexR<length) { |
| + if(key!=NULL) { |
| + *key=RES_GET_KEY16(pResData, p[indexR]); |
| + } |
| + return makeResourceFrom16(pResData, p[length+indexR]); |
| + } |
| + break; |
| + } |
| + case URES_TABLE32: { |
| + if (offset != 0) { /* empty if offset==0 */ |
| + const int32_t *p= pResData->pRoot+offset; |
| + length=*p++; |
| + if(indexR<length) { |
| + if(key!=NULL) { |
| + *key=RES_GET_KEY32(pResData, p[indexR]); |
| + } |
| + return (Resource)p[length+indexR]; |
| + } |
| + } |
| + break; |
| + } |
| + default: |
| + break; |
| + } |
| + return RES_BOGUS; |
| +} |
| + |
| +U_CAPI Resource U_EXPORT2 |
| +res_getResource(const ResourceData *pResData, const char *key) { |
| + const char *realKey=key; |
| + int32_t idx; |
| + return res_getTableItemByKey(pResData, pResData->rootRes, &idx, &realKey); |
| +} |
| + |
| +// TODO: Ported from Java, but enumerating at this low level may prevent us |
| +// from doing necessary things, like resolving aliases, |
| +// which need access to higher-level UResourceBundle code. |
| +// Consider porting the low-level Container/Array/Table classes from Java, |
| +// with getters for keys and values, |
| +// and doing the enumeration in the higher-level code on top of those accessors. |
| +U_CFUNC void |
| +ures_getAllTableItems(const ResourceData *pResData, Resource table, |
| + icu::ResourceDataValue &value, icu::ResourceTableSink &sink, |
| + UErrorCode &errorCode) { |
| + if(U_FAILURE(errorCode)) { return; } |
| + const uint16_t *keys16 = NULL; |
| + const int32_t *keys32 = NULL; |
| + const uint16_t *items16 = NULL; |
| + const Resource *items32 = NULL; |
| + uint32_t offset = RES_GET_OFFSET(table); |
| + int32_t length = 0; |
| + switch(RES_GET_TYPE(table)) { |
| + case URES_TABLE: { |
| + if (offset != 0) { /* empty if offset==0 */ |
| + keys16 = (const uint16_t *)(pResData->pRoot+offset); |
| + length = *keys16++; |
| + items32 = (const Resource *)(keys16+length+(~length&1)); |
| + } |
| + break; |
| + } |
| + case URES_TABLE16: { |
| + keys16 = pResData->p16BitUnits+offset; |
| + length = *keys16++; |
| + items16 = keys16 + length; |
| + break; |
| + } |
| + case URES_TABLE32: { |
| + if (offset != 0) { /* empty if offset==0 */ |
| + keys32 = pResData->pRoot+offset; |
| + length = *keys32++; |
| + items32 = (const Resource *)keys32 + length; |
| + } |
| + break; |
| + } |
| + default: |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + return; |
| + } |
| + |
| + for (int32_t i = 0; i < length; ++i) { |
| + const char *key; |
| + if (keys16 != NULL) { |
| + key=RES_GET_KEY16(pResData, keys16[i]); |
| + } else { |
| + key=RES_GET_KEY32(pResData, keys32[i]); |
| + } |
| + Resource res; |
| + if (items16 != NULL) { |
| + res = makeResourceFrom16(pResData, items16[i]); |
| + } else { |
| + res = items32[i]; |
| + } |
| + int32_t type = RES_GET_TYPE(res); |
| + if (URES_IS_ARRAY(type)) { |
| + int32_t numItems = getArrayLength(pResData, res); |
| + icu::ResourceArraySink *subSink = sink.getOrCreateArraySink(key, numItems, errorCode); |
| + if (subSink != NULL) { |
| + ures_getAllArrayItems(pResData, res, value, *subSink, errorCode); |
| + } |
| + } else if (URES_IS_TABLE(type)) { |
| + int32_t numItems = getTableLength(pResData, res); |
| + icu::ResourceTableSink *subSink = sink.getOrCreateTableSink(key, numItems, errorCode); |
| + if (subSink != NULL) { |
| + ures_getAllTableItems(pResData, res, value, *subSink, errorCode); |
| + } |
| + /* TODO: settle on how to deal with aliases, port to Java |
| + } else if (type == URES_ALIAS) { |
| + // aliases not handled in resource enumeration |
| + errorCode = U_UNSUPPORTED_ERROR; |
| + return; */ |
| + } else if (isNoInheritanceMarker(pResData, res)) { |
| + sink.putNoFallback(key, errorCode); |
| + } else { |
| + value.setResource(res); |
| + sink.put(key, value, errorCode); |
| + } |
| + if(U_FAILURE(errorCode)) { return; } |
| + } |
| + sink.leave(errorCode); |
| +} |
| + |
| +U_CAPI Resource U_EXPORT2 |
| +res_getArrayItem(const ResourceData *pResData, Resource array, int32_t indexR) { |
| + uint32_t offset=RES_GET_OFFSET(array); |
| + U_ASSERT(indexR>=0); /* to ensure the index is not negative */ |
| + switch(RES_GET_TYPE(array)) { |
| + case URES_ARRAY: { |
| + if (offset!=0) { /* empty if offset==0 */ |
| + const int32_t *p= pResData->pRoot+offset; |
| + if(indexR<*p) { |
| + return (Resource)p[1+indexR]; |
| + } |
| + } |
| + break; |
| + } |
| + case URES_ARRAY16: { |
| + const uint16_t *p=pResData->p16BitUnits+offset; |
| + if(indexR<*p) { |
| + return makeResourceFrom16(pResData, p[1+indexR]); |
| + } |
| + break; |
| + } |
| + default: |
| + break; |
| + } |
| + return RES_BOGUS; |
| +} |
| + |
| +U_CFUNC void |
| +ures_getAllArrayItems(const ResourceData *pResData, Resource array, |
| + icu::ResourceDataValue &value, icu::ResourceArraySink &sink, |
| + UErrorCode &errorCode) { |
| + if(U_FAILURE(errorCode)) { return; } |
| + const uint16_t *items16 = NULL; |
| + const Resource *items32 = NULL; |
| + uint32_t offset=RES_GET_OFFSET(array); |
| + int32_t length = 0; |
| + switch(RES_GET_TYPE(array)) { |
| + case URES_ARRAY: { |
| + if (offset!=0) { /* empty if offset==0 */ |
| + items32 = (const Resource *)pResData->pRoot+offset; |
| + length = *items32++; |
| + } |
| + break; |
| + } |
| + case URES_ARRAY16: { |
| + items16 = pResData->p16BitUnits+offset; |
| + length = *items16++; |
| + break; |
| + } |
| + default: |
| + errorCode = U_RESOURCE_TYPE_MISMATCH; |
| + return; |
| + } |
| + |
| + for (int32_t i = 0; i < length; ++i) { |
| + Resource res; |
| + if (items16 != NULL) { |
| + res = makeResourceFrom16(pResData, items16[i]); |
| + } else { |
| + res = items32[i]; |
| + } |
| + int32_t type = RES_GET_TYPE(res); |
| + if (URES_IS_ARRAY(type)) { |
| + int32_t numItems = getArrayLength(pResData, res); |
| + icu::ResourceArraySink *subSink = sink.getOrCreateArraySink(i, numItems, errorCode); |
| + if (subSink != NULL) { |
| + ures_getAllArrayItems(pResData, res, value, *subSink, errorCode); |
| + } |
| + } else if (URES_IS_TABLE(type)) { |
| + int32_t numItems = getTableLength(pResData, res); |
| + icu::ResourceTableSink *subSink = sink.getOrCreateTableSink(i, numItems, errorCode); |
| + if (subSink != NULL) { |
| + ures_getAllTableItems(pResData, res, value, *subSink, errorCode); |
| + } |
| + /* TODO: settle on how to deal with aliases, port to Java |
| + } else if (type == URES_ALIAS) { |
| + // aliases not handled in resource enumeration |
| + errorCode = U_UNSUPPORTED_ERROR; |
| + return; */ |
| + } else { |
| + value.setResource(res); |
| + sink.put(i, value, errorCode); |
| + } |
| + if(U_FAILURE(errorCode)) { return; } |
| + } |
| + sink.leave(errorCode); |
| +} |
| + |
| +U_CFUNC Resource |
| +res_findResource(const ResourceData *pResData, Resource r, char** path, const char** key) { |
| + char *pathP = *path, *nextSepP = *path; |
| + char *closeIndex = NULL; |
| + Resource t1 = r; |
| + Resource t2; |
| + int32_t indexR = 0; |
| + UResType type = (UResType)RES_GET_TYPE(t1); |
| + |
| + /* if you come in with an empty path, you'll be getting back the same resource */ |
| + if(!uprv_strlen(pathP)) { |
| + return r; |
| + } |
| + |
| + /* one needs to have an aggregate resource in order to search in it */ |
| + if(!URES_IS_CONTAINER(type)) { |
| + return RES_BOGUS; |
| + } |
| + |
| + while(nextSepP && *pathP && t1 != RES_BOGUS && URES_IS_CONTAINER(type)) { |
| + /* Iteration stops if: the path has been consumed, we found a non-existing |
| + * resource (t1 == RES_BOGUS) or we found a scalar resource (including alias) |
| + */ |
| + nextSepP = uprv_strchr(pathP, RES_PATH_SEPARATOR); |
| + /* if there are more separators, terminate string |
| + * and set path to the remaining part of the string |
| + */ |
| + if(nextSepP != NULL) { |
| + if(nextSepP == pathP) { |
| + // Empty key string. |
| + return RES_BOGUS; |
| + } |
| + *nextSepP = 0; /* overwrite the separator with a NUL to terminate the key */ |
| + *path = nextSepP+1; |
| + } else { |
| + *path = uprv_strchr(pathP, 0); |
| + } |
| + |
| + /* if the resource is a table */ |
| + /* try the key based access */ |
| + if(URES_IS_TABLE(type)) { |
| + *key = pathP; |
| + t2 = res_getTableItemByKey(pResData, t1, &indexR, key); |
| + if(t2 == RES_BOGUS) { |
| + /* if we fail to get the resource by key, maybe we got an index */ |
| + indexR = uprv_strtol(pathP, &closeIndex, 10); |
| + if(*closeIndex == 0) { |
| + /* if we indeed have an index, try to get the item by index */ |
| + t2 = res_getTableItemByIndex(pResData, t1, indexR, key); |
| + } |
| + } |
| + } else if(URES_IS_ARRAY(type)) { |
| + indexR = uprv_strtol(pathP, &closeIndex, 10); |
| + if(*closeIndex == 0) { |
| + t2 = res_getArrayItem(pResData, t1, indexR); |
| + } else { |
| + t2 = RES_BOGUS; /* have an array, but don't have a valid index */ |
| + } |
| + *key = NULL; |
| + } else { /* can't do much here, except setting t2 to bogus */ |
| + t2 = RES_BOGUS; |
| + } |
| + t1 = t2; |
| + type = (UResType)RES_GET_TYPE(t1); |
| + /* position pathP to next resource key/index */ |
| + pathP = *path; |
| + } |
| + |
| + return t1; |
| +} |
| + |
| +/* resource bundle swapping ------------------------------------------------- */ |
| + |
| +/* |
| + * Need to always enumerate the entire item tree, |
| + * track the lowest address of any item to use as the limit for char keys[], |
| + * track the highest address of any item to return the size of the data. |
| + * |
| + * We should have thought of storing those in the data... |
| + * It is possible to extend the data structure by putting additional values |
| + * in places that are inaccessible by ordinary enumeration of the item tree. |
| + * For example, additional integers could be stored at the beginning or |
| + * end of the key strings; this could be indicated by a minor version number, |
| + * and the data swapping would have to know about these values. |
| + * |
| + * The data structure does not forbid keys to be shared, so we must swap |
| + * all keys once instead of each key when it is referenced. |
| + * |
| + * These swapping functions assume that a resource bundle always has a length |
| + * that is a multiple of 4 bytes. |
| + * Currently, this is trivially true because genrb writes bundle tree leaves |
| + * physically first, before their branches, so that the root table with its |
| + * array of resource items (uint32_t values) is always last. |
| + */ |
| + |
| +/* definitions for table sorting ------------------------ */ |
| + |
| +/* |
| + * row of a temporary array |
| + * |
| + * gets platform-endian key string indexes and sorting indexes; |
| + * after sorting this array by keys, the actual key/value arrays are permutated |
| + * according to the sorting indexes |
| + */ |
| +typedef struct Row { |
| + int32_t keyIndex, sortIndex; |
| +} Row; |
| + |
| +static int32_t |
| +ures_compareRows(const void *context, const void *left, const void *right) { |
| + const char *keyChars=(const char *)context; |
| + return (int32_t)uprv_strcmp(keyChars+((const Row *)left)->keyIndex, |
| + keyChars+((const Row *)right)->keyIndex); |
| +} |
| + |
| +typedef struct TempTable { |
| + const char *keyChars; |
| + Row *rows; |
| + int32_t *resort; |
| + uint32_t *resFlags; |
| + int32_t localKeyLimit; |
| + uint8_t majorFormatVersion; |
| +} TempTable; |
| + |
| +enum { |
| + STACK_ROW_CAPACITY=200 |
| +}; |
| + |
| +/* The table item key string is not locally available. */ |
| +static const char *const gUnknownKey=""; |
| + |
| +/* resource table key for collation binaries: "%%CollationBin" */ |
| +static const UChar gCollationBinKey[]={ |
| + 0x25, 0x25, |
| + 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, |
| + 0x42, 0x69, 0x6e, |
| + 0 |
| +}; |
| + |
| +/* |
| + * swap one resource item |
| + */ |
| +static void |
| +ures_swapResource(const UDataSwapper *ds, |
| + const Resource *inBundle, Resource *outBundle, |
| + Resource res, /* caller swaps res itself */ |
| + const char *key, |
| + TempTable *pTempTable, |
| + UErrorCode *pErrorCode) { |
| + const Resource *p; |
| + Resource *q; |
| + int32_t offset, count; |
| + |
| + switch(RES_GET_TYPE(res)) { |
| + case URES_TABLE16: |
| + case URES_STRING_V2: |
| + case URES_INT: |
| + case URES_ARRAY16: |
| + /* integer, or points to 16-bit units, nothing to do here */ |
| + return; |
| + default: |
| + break; |
| + } |
| + |
| + /* all other types use an offset to point to their data */ |
| + offset=(int32_t)RES_GET_OFFSET(res); |
| + if(offset==0) { |
| + /* special offset indicating an empty item */ |
| + return; |
| + } |
| + if(pTempTable->resFlags[offset>>5]&((uint32_t)1<<(offset&0x1f))) { |
| + /* we already swapped this resource item */ |
| + return; |
| + } else { |
| + /* mark it as swapped now */ |
| + pTempTable->resFlags[offset>>5]|=((uint32_t)1<<(offset&0x1f)); |
| + } |
| + |
| + p=inBundle+offset; |
| + q=outBundle+offset; |
| + |
| + switch(RES_GET_TYPE(res)) { |
| + case URES_ALIAS: |
| + /* physically same value layout as string, fall through */ |
| + case URES_STRING: |
| + count=udata_readInt32(ds, (int32_t)*p); |
| + /* swap length */ |
| + ds->swapArray32(ds, p, 4, q, pErrorCode); |
| + /* swap each UChar (the terminating NUL would not change) */ |
| + ds->swapArray16(ds, p+1, 2*count, q+1, pErrorCode); |
| + break; |
| + case URES_BINARY: |
| + count=udata_readInt32(ds, (int32_t)*p); |
| + /* swap length */ |
| + ds->swapArray32(ds, p, 4, q, pErrorCode); |
| + /* no need to swap or copy bytes - ures_swap() copied them all */ |
| + |
| + /* swap known formats */ |
| +#if !UCONFIG_NO_COLLATION |
| + if( key!=NULL && /* the binary is in a table */ |
| + (key!=gUnknownKey ? |
| + /* its table key string is "%%CollationBin" */ |
| + 0==ds->compareInvChars(ds, key, -1, |
| + gCollationBinKey, UPRV_LENGTHOF(gCollationBinKey)-1) : |
| + /* its table key string is unknown but it looks like a collation binary */ |
| + ucol_looksLikeCollationBinary(ds, p+1, count)) |
| + ) { |
| + ucol_swap(ds, p+1, count, q+1, pErrorCode); |
| + } |
| +#endif |
| + break; |
| + case URES_TABLE: |
| + case URES_TABLE32: |
| + { |
| + const uint16_t *pKey16; |
| + uint16_t *qKey16; |
| + |
| + const int32_t *pKey32; |
| + int32_t *qKey32; |
| + |
| + Resource item; |
| + int32_t i, oldIndex; |
| + |
| + if(RES_GET_TYPE(res)==URES_TABLE) { |
| + /* get table item count */ |
| + pKey16=(const uint16_t *)p; |
| + qKey16=(uint16_t *)q; |
| + count=ds->readUInt16(*pKey16); |
| + |
| + pKey32=qKey32=NULL; |
| + |
| + /* swap count */ |
| + ds->swapArray16(ds, pKey16++, 2, qKey16++, pErrorCode); |
| + |
| + offset+=((1+count)+1)/2; |
| + } else { |
| + /* get table item count */ |
| + pKey32=(const int32_t *)p; |
| + qKey32=(int32_t *)q; |
| + count=udata_readInt32(ds, *pKey32); |
| + |
| + pKey16=qKey16=NULL; |
| + |
| + /* swap count */ |
| + ds->swapArray32(ds, pKey32++, 4, qKey32++, pErrorCode); |
| + |
| + offset+=1+count; |
| + } |
| + |
| + if(count==0) { |
| + break; |
| + } |
| + |
| + p=inBundle+offset; /* pointer to table resources */ |
| + q=outBundle+offset; |
| + |
| + /* recurse */ |
| + for(i=0; i<count; ++i) { |
| + const char *itemKey=gUnknownKey; |
| + if(pKey16!=NULL) { |
| + int32_t keyOffset=ds->readUInt16(pKey16[i]); |
| + if(keyOffset<pTempTable->localKeyLimit) { |
| + itemKey=(const char *)outBundle+keyOffset; |
| + } |
| + } else { |
| + int32_t keyOffset=udata_readInt32(ds, pKey32[i]); |
| + if(keyOffset>=0) { |
| + itemKey=(const char *)outBundle+keyOffset; |
| + } |
| + } |
| + item=ds->readUInt32(p[i]); |
| + ures_swapResource(ds, inBundle, outBundle, item, itemKey, pTempTable, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swapResource(table res=%08x)[%d].recurse(%08x) failed\n", |
| + res, i, item); |
| + return; |
| + } |
| + } |
| + |
| + if(pTempTable->majorFormatVersion>1 || ds->inCharset==ds->outCharset) { |
| + /* no need to sort, just swap the offset/value arrays */ |
| + if(pKey16!=NULL) { |
| + ds->swapArray16(ds, pKey16, count*2, qKey16, pErrorCode); |
| + ds->swapArray32(ds, p, count*4, q, pErrorCode); |
| + } else { |
| + /* swap key offsets and items as one array */ |
| + ds->swapArray32(ds, pKey32, count*2*4, qKey32, pErrorCode); |
| + } |
| + break; |
| + } |
| + |
| + /* |
| + * We need to sort tables by outCharset key strings because they |
| + * sort differently for different charset families. |
| + * ures_swap() already set pTempTable->keyChars appropriately. |
| + * First we set up a temporary table with the key indexes and |
| + * sorting indexes and sort that. |
| + * Then we permutate and copy/swap the actual values. |
| + */ |
| + if(pKey16!=NULL) { |
| + for(i=0; i<count; ++i) { |
| + pTempTable->rows[i].keyIndex=ds->readUInt16(pKey16[i]); |
| + pTempTable->rows[i].sortIndex=i; |
| + } |
| + } else { |
| + for(i=0; i<count; ++i) { |
| + pTempTable->rows[i].keyIndex=udata_readInt32(ds, pKey32[i]); |
| + pTempTable->rows[i].sortIndex=i; |
| + } |
| + } |
| + uprv_sortArray(pTempTable->rows, count, sizeof(Row), |
| + ures_compareRows, pTempTable->keyChars, |
| + FALSE, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swapResource(table res=%08x).uprv_sortArray(%d items) failed\n", |
| + res, count); |
| + return; |
| + } |
| + |
| + /* |
| + * copy/swap/permutate items |
| + * |
| + * If we swap in-place, then the permutation must use another |
| + * temporary array (pTempTable->resort) |
| + * before the results are copied to the outBundle. |
| + */ |
| + /* keys */ |
| + if(pKey16!=NULL) { |
| + uint16_t *rKey16; |
| + |
| + if(pKey16!=qKey16) { |
| + rKey16=qKey16; |
| + } else { |
| + rKey16=(uint16_t *)pTempTable->resort; |
| + } |
| + for(i=0; i<count; ++i) { |
| + oldIndex=pTempTable->rows[i].sortIndex; |
| + ds->swapArray16(ds, pKey16+oldIndex, 2, rKey16+i, pErrorCode); |
| + } |
| + if(qKey16!=rKey16) { |
| + uprv_memcpy(qKey16, rKey16, 2*count); |
| + } |
| + } else { |
| + int32_t *rKey32; |
| + |
| + if(pKey32!=qKey32) { |
| + rKey32=qKey32; |
| + } else { |
| + rKey32=pTempTable->resort; |
| + } |
| + for(i=0; i<count; ++i) { |
| + oldIndex=pTempTable->rows[i].sortIndex; |
| + ds->swapArray32(ds, pKey32+oldIndex, 4, rKey32+i, pErrorCode); |
| + } |
| + if(qKey32!=rKey32) { |
| + uprv_memcpy(qKey32, rKey32, 4*count); |
| + } |
| + } |
| + |
| + /* resources */ |
| + { |
| + Resource *r; |
| + |
| + |
| + if(p!=q) { |
| + r=q; |
| + } else { |
| + r=(Resource *)pTempTable->resort; |
| + } |
| + for(i=0; i<count; ++i) { |
| + oldIndex=pTempTable->rows[i].sortIndex; |
| + ds->swapArray32(ds, p+oldIndex, 4, r+i, pErrorCode); |
| + } |
| + if(q!=r) { |
| + uprv_memcpy(q, r, 4*count); |
| + } |
| + } |
| + } |
| + break; |
| + case URES_ARRAY: |
| + { |
| + Resource item; |
| + int32_t i; |
| + |
| + count=udata_readInt32(ds, (int32_t)*p); |
| + /* swap length */ |
| + ds->swapArray32(ds, p++, 4, q++, pErrorCode); |
| + |
| + /* recurse */ |
| + for(i=0; i<count; ++i) { |
| + item=ds->readUInt32(p[i]); |
| + ures_swapResource(ds, inBundle, outBundle, item, NULL, pTempTable, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swapResource(array res=%08x)[%d].recurse(%08x) failed\n", |
| + res, i, item); |
| + return; |
| + } |
| + } |
| + |
| + /* swap items */ |
| + ds->swapArray32(ds, p, 4*count, q, pErrorCode); |
| + } |
| + break; |
| + case URES_INT_VECTOR: |
| + count=udata_readInt32(ds, (int32_t)*p); |
| + /* swap length and each integer */ |
| + ds->swapArray32(ds, p, 4*(1+count), q, pErrorCode); |
| + break; |
| + default: |
| + /* also catches RES_BOGUS */ |
| + *pErrorCode=U_UNSUPPORTED_ERROR; |
| + break; |
| + } |
| +} |
| + |
| +U_CAPI int32_t U_EXPORT2 |
| +ures_swap(const UDataSwapper *ds, |
| + const void *inData, int32_t length, void *outData, |
| + UErrorCode *pErrorCode) { |
| + const UDataInfo *pInfo; |
| + const Resource *inBundle; |
| + Resource rootRes; |
| + int32_t headerSize, maxTableLength; |
| + |
| + Row rows[STACK_ROW_CAPACITY]; |
| + int32_t resort[STACK_ROW_CAPACITY]; |
| + TempTable tempTable; |
| + |
| + const int32_t *inIndexes; |
| + |
| + /* the following integers count Resource item offsets (4 bytes each), not bytes */ |
| + int32_t bundleLength, indexLength, keysBottom, keysTop, resBottom, top; |
| + |
| + /* udata_swapDataHeader checks the arguments */ |
| + headerSize=udata_swapDataHeader(ds, inData, length, outData, pErrorCode); |
| + if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { |
| + return 0; |
| + } |
| + |
| + /* check data format and format version */ |
| + pInfo=(const UDataInfo *)((const char *)inData+4); |
| + if(!( |
| + pInfo->dataFormat[0]==0x52 && /* dataFormat="ResB" */ |
| + pInfo->dataFormat[1]==0x65 && |
| + pInfo->dataFormat[2]==0x73 && |
| + pInfo->dataFormat[3]==0x42 && |
| + /* formatVersion 1.1+ or 2.x or 3.x */ |
| + ((pInfo->formatVersion[0]==1 && pInfo->formatVersion[1]>=1) || |
| + pInfo->formatVersion[0]==2 || pInfo->formatVersion[0]==3) |
| + )) { |
| + udata_printError(ds, "ures_swap(): data format %02x.%02x.%02x.%02x (format version %02x.%02x) is not a resource bundle\n", |
| + pInfo->dataFormat[0], pInfo->dataFormat[1], |
| + pInfo->dataFormat[2], pInfo->dataFormat[3], |
| + pInfo->formatVersion[0], pInfo->formatVersion[1]); |
| + *pErrorCode=U_UNSUPPORTED_ERROR; |
| + return 0; |
| + } |
| + tempTable.majorFormatVersion=pInfo->formatVersion[0]; |
| + |
| + /* a resource bundle must contain at least one resource item */ |
| + if(length<0) { |
| + bundleLength=-1; |
| + } else { |
| + bundleLength=(length-headerSize)/4; |
| + |
| + /* formatVersion 1.1 must have a root item and at least 5 indexes */ |
| + if(bundleLength<(1+5)) { |
| + udata_printError(ds, "ures_swap(): too few bytes (%d after header) for a resource bundle\n", |
| + length-headerSize); |
| + *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| + return 0; |
| + } |
| + } |
| + |
| + inBundle=(const Resource *)((const char *)inData+headerSize); |
| + rootRes=ds->readUInt32(*inBundle); |
| + |
| + /* formatVersion 1.1 adds the indexes[] array */ |
| + inIndexes=(const int32_t *)(inBundle+1); |
| + |
| + indexLength=udata_readInt32(ds, inIndexes[URES_INDEX_LENGTH])&0xff; |
| + if(indexLength<=URES_INDEX_MAX_TABLE_LENGTH) { |
| + udata_printError(ds, "ures_swap(): too few indexes for a 1.1+ resource bundle\n"); |
| + *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| + return 0; |
| + } |
| + keysBottom=1+indexLength; |
| + keysTop=udata_readInt32(ds, inIndexes[URES_INDEX_KEYS_TOP]); |
| + if(indexLength>URES_INDEX_16BIT_TOP) { |
| + resBottom=udata_readInt32(ds, inIndexes[URES_INDEX_16BIT_TOP]); |
| + } else { |
| + resBottom=keysTop; |
| + } |
| + top=udata_readInt32(ds, inIndexes[URES_INDEX_BUNDLE_TOP]); |
| + maxTableLength=udata_readInt32(ds, inIndexes[URES_INDEX_MAX_TABLE_LENGTH]); |
| + |
| + if(0<=bundleLength && bundleLength<top) { |
| + udata_printError(ds, "ures_swap(): resource top %d exceeds bundle length %d\n", |
| + top, bundleLength); |
| + *pErrorCode=U_INDEX_OUTOFBOUNDS_ERROR; |
| + return 0; |
| + } |
| + if(keysTop>(1+indexLength)) { |
| + tempTable.localKeyLimit=keysTop<<2; |
| + } else { |
| + tempTable.localKeyLimit=0; |
| + } |
| + |
| + if(length>=0) { |
| + Resource *outBundle=(Resource *)((char *)outData+headerSize); |
| + |
| + /* track which resources we have already swapped */ |
| + uint32_t stackResFlags[STACK_ROW_CAPACITY]; |
| + int32_t resFlagsLength; |
| + |
| + /* |
| + * We need one bit per 4 resource bundle bytes so that we can track |
| + * every possible Resource for whether we have swapped it already. |
| + * Multiple Resource words can refer to the same bundle offsets |
| + * for sharing identical values. |
| + * We could optimize this by allocating only for locations above |
| + * where Resource values are stored (above keys & strings). |
| + */ |
| + resFlagsLength=(length+31)>>5; /* number of bytes needed */ |
| + resFlagsLength=(resFlagsLength+3)&~3; /* multiple of 4 bytes for uint32_t */ |
| + if(resFlagsLength<=(int32_t)sizeof(stackResFlags)) { |
| + tempTable.resFlags=stackResFlags; |
| + } else { |
| + tempTable.resFlags=(uint32_t *)uprv_malloc(resFlagsLength); |
| + if(tempTable.resFlags==NULL) { |
| + udata_printError(ds, "ures_swap(): unable to allocate memory for tracking resources\n"); |
| + *pErrorCode=U_MEMORY_ALLOCATION_ERROR; |
| + return 0; |
| + } |
| + } |
| + uprv_memset(tempTable.resFlags, 0, resFlagsLength); |
| + |
| + /* copy the bundle for binary and inaccessible data */ |
| + if(inData!=outData) { |
| + uprv_memcpy(outBundle, inBundle, 4*top); |
| + } |
| + |
| + /* swap the key strings, but not the padding bytes (0xaa) after the last string and its NUL */ |
| + udata_swapInvStringBlock(ds, inBundle+keysBottom, 4*(keysTop-keysBottom), |
| + outBundle+keysBottom, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swap().udata_swapInvStringBlock(keys[%d]) failed\n", 4*(keysTop-keysBottom)); |
| + return 0; |
| + } |
| + |
| + /* swap the 16-bit units (strings, table16, array16) */ |
| + if(keysTop<resBottom) { |
| + ds->swapArray16(ds, inBundle+keysTop, (resBottom-keysTop)*4, outBundle+keysTop, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swap().swapArray16(16-bit units[%d]) failed\n", 2*(resBottom-keysTop)); |
| + return 0; |
| + } |
| + } |
| + |
| + /* allocate the temporary table for sorting resource tables */ |
| + tempTable.keyChars=(const char *)outBundle; /* sort by outCharset */ |
| + if(tempTable.majorFormatVersion>1 || maxTableLength<=STACK_ROW_CAPACITY) { |
| + tempTable.rows=rows; |
| + tempTable.resort=resort; |
| + } else { |
| + tempTable.rows=(Row *)uprv_malloc(maxTableLength*sizeof(Row)+maxTableLength*4); |
| + if(tempTable.rows==NULL) { |
| + udata_printError(ds, "ures_swap(): unable to allocate memory for sorting tables (max length: %d)\n", |
| + maxTableLength); |
| + *pErrorCode=U_MEMORY_ALLOCATION_ERROR; |
| + if(tempTable.resFlags!=stackResFlags) { |
| + uprv_free(tempTable.resFlags); |
| + } |
| + return 0; |
| + } |
| + tempTable.resort=(int32_t *)(tempTable.rows+maxTableLength); |
| + } |
| + |
| + /* swap the resources */ |
| + ures_swapResource(ds, inBundle, outBundle, rootRes, NULL, &tempTable, pErrorCode); |
| + if(U_FAILURE(*pErrorCode)) { |
| + udata_printError(ds, "ures_swapResource(root res=%08x) failed\n", |
| + rootRes); |
| + } |
| + |
| + if(tempTable.rows!=rows) { |
| + uprv_free(tempTable.rows); |
| + } |
| + if(tempTable.resFlags!=stackResFlags) { |
| + uprv_free(tempTable.resFlags); |
| + } |
| + |
| + /* swap the root resource and indexes */ |
| + ds->swapArray32(ds, inBundle, keysBottom*4, outBundle, pErrorCode); |
| + } |
| + |
| + return headerSize+4*top; |
| +} |
| diff --git a/source/common/uresdata.h b/source/common/uresdata.h |
| index ff69dd4..1afa77c 100644 |
| --- a/source/common/uresdata.h |
| +++ b/source/common/uresdata.h |
| @@ -453,11 +453,67 @@ res_getTableItemByIndex(const ResourceData *pResData, Resource table, int32_t in |
| U_INTERNAL Resource U_EXPORT2 |
| res_getTableItemByKey(const ResourceData *pResData, Resource table, int32_t *indexS, const char* * key); |
| |
| -/* |
| +/** |
| + * Iterates over the path and stops when a scalar resource is found. |
| + * Follows aliases. |
| * Modifies the contents of *path (replacing separators with NULs), |
| * and also moves *path forward while it finds items. |
| + * |
| + * @param path input: "CollationElements/Sequence" or "zoneStrings/3/2" etc.; |
| + * output: points to the part that has not yet been processed |
| + */ |
| +U_CFUNC Resource res_findResource(const ResourceData *pResData, Resource r, |
| + char** path, const char** key); |
| + |
| +#ifdef __cplusplus |
| + |
| +#include "resource.h" |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +class ResourceDataValue : public ResourceValue { |
| +public: |
| + ResourceDataValue() : pResData(NULL), res(URES_NONE) {} |
| + virtual ~ResourceDataValue(); |
| + |
| + void setData(const ResourceData *data) { pResData = data; } |
| + void setResource(Resource r) { res = r; } |
| + |
| + virtual UResType getType() const; |
| + virtual const UChar *getString(int32_t &length, UErrorCode &errorCode) const; |
| + virtual const UChar *getAliasString(int32_t &length, UErrorCode &errorCode) const; |
| + virtual int32_t getInt(UErrorCode &errorCode) const; |
| + virtual uint32_t getUInt(UErrorCode &errorCode) const; |
| + virtual const int32_t *getIntVector(int32_t &length, UErrorCode &errorCode) const; |
| + virtual const uint8_t *getBinary(int32_t &length, UErrorCode &errorCode) const; |
| + |
| + const ResourceData *pResData; |
| + |
| +private: |
| + Resource res; |
| +}; |
| + |
| +U_NAMESPACE_END |
| + |
| +/** |
| + * @param value will be set during enumeration; input contents is ignored |
| + * @param sink receives all table item key-value pairs |
| */ |
| -U_CFUNC Resource res_findResource(const ResourceData *pResData, Resource r, char** path, const char** key); |
| +U_CFUNC void |
| +ures_getAllTableItems(const ResourceData *pResData, Resource table, |
| + icu::ResourceDataValue &value, icu::ResourceTableSink &sink, |
| + UErrorCode &errorCode); |
| + |
| +/** |
| + * @param value will be set during enumeration; input contents is ignored |
| + * @param sink receives all array item values |
| + */ |
| +U_CFUNC void |
| +ures_getAllArrayItems(const ResourceData *pResData, Resource array, |
| + icu::ResourceDataValue &value, icu::ResourceArraySink &sink, |
| + UErrorCode &errorCode); |
| + |
| +#endif /* __cplusplus */ |
| |
| /** |
| * Swap an ICU resource bundle. See udataswp.h. |
| diff --git a/source/common/uresimp.h b/source/common/uresimp.h |
| index b8ec5a6..6b264db 100644 |
| --- a/source/common/uresimp.h |
| +++ b/source/common/uresimp.h |
| @@ -1,6 +1,6 @@ |
| /* |
| ********************************************************************** |
| -* Copyright (C) 2000-2014, International Business Machines |
| +* Copyright (C) 2000-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| */ |
| @@ -222,6 +222,18 @@ ures_getStringByKeyWithFallback(const UResourceBundle *resB, |
| int32_t* len, |
| UErrorCode *status); |
| |
| +#ifdef __cplusplus |
| + |
| +U_CAPI void U_EXPORT2 |
| +ures_getAllArrayItemsWithFallback(const UResourceBundle *bundle, const char *path, |
| + icu::ResourceArraySink &sink, UErrorCode &errorCode); |
| + |
| +U_CAPI void U_EXPORT2 |
| +ures_getAllTableItemsWithFallback(const UResourceBundle *bundle, const char *path, |
| + icu::ResourceTableSink &sink, UErrorCode &errorCode); |
| + |
| +#endif /* __cplusplus */ |
| + |
| /** |
| * Get a version number by key |
| * @param resB bundle containing version number |
| diff --git a/source/common/uresource.cpp b/source/common/uresource.cpp |
| new file mode 100644 |
| index 0000000..e69de29 |
| diff --git a/source/common/uresource.h b/source/common/uresource.h |
| new file mode 100644 |
| index 0000000..e69de29 |
| diff --git a/source/data/unit/ar.txt b/source/data/unit/ar.txt |
| index 883e97c..d6a1d3f 100755 |
| --- a/source/data/unit/ar.txt |
| +++ b/source/data/unit/ar.txt |
| @@ -400,7 +400,7 @@ ar{ |
| many{"{0} سنة"} |
| one{"سنة"} |
| other{"{0} سنة"} |
| - per{"في السنة"} |
| + per{"{0} في السنة"} |
| two{"سنتان"} |
| zero{"{0} سنة"} |
| } |
| diff --git a/source/data/unit/de.txt b/source/data/unit/de.txt |
| index 9ec6e5d..eab9dc1 100755 |
| --- a/source/data/unit/de.txt |
| +++ b/source/data/unit/de.txt |
| @@ -467,7 +467,7 @@ de{ |
| dnam{"Unzen"} |
| one{"{0} Unze"} |
| other{"{0} Unzen"} |
| - per{"pro Unze"} |
| + per{"{0} pro Unze"} |
| } |
| ounce-troy{ |
| dnam{"Feinunzen"} |
| diff --git a/source/data/unit/dsb.txt b/source/data/unit/dsb.txt |
| index fbdfc9f..d972a0d 100755 |
| --- a/source/data/unit/dsb.txt |
| +++ b/source/data/unit/dsb.txt |
| @@ -226,7 +226,7 @@ dsb{ |
| few{"{0} góźiny"} |
| one{"{0} góźina"} |
| other{"{0} góźinow"} |
| - per{"na góźinu"} |
| + per{"{0} na góźinu"} |
| two{"{0} góźinje"} |
| } |
| microsecond{ |
| @@ -269,7 +269,7 @@ dsb{ |
| few{"{0} sekundy"} |
| one{"{0} sekunda"} |
| other{"{0} sekundow"} |
| - per{"na sekundu"} |
| + per{"{0} na sekundu"} |
| two{"{0} sekunźe"} |
| } |
| week{ |
| diff --git a/source/data/unit/hsb.txt b/source/data/unit/hsb.txt |
| index 747481e..bdf5112 100755 |
| --- a/source/data/unit/hsb.txt |
| +++ b/source/data/unit/hsb.txt |
| @@ -226,7 +226,7 @@ hsb{ |
| few{"{0} hodźiny"} |
| one{"{0} hodźina"} |
| other{"{0} hodźinow"} |
| - per{"na hodźinu"} |
| + per{"{0} na hodźinu"} |
| two{"{0} hodźinje"} |
| } |
| microsecond{ |
| @@ -269,7 +269,7 @@ hsb{ |
| few{"{0} sekundy"} |
| one{"{0} sekunda"} |
| other{"{0} sekundow"} |
| - per{"na sekundu"} |
| + per{"{0} na sekundu"} |
| two{"{0} sekundźe"} |
| } |
| week{ |
| diff --git a/source/data/unit/lv.txt b/source/data/unit/lv.txt |
| index 81e8a23..1536fca 100755 |
| --- a/source/data/unit/lv.txt |
| +++ b/source/data/unit/lv.txt |
| @@ -77,7 +77,7 @@ lv{ |
| dnam{"kvadrātcentimetri"} |
| one{"{0} kvadrātcentimetrs"} |
| other{"{0} kvadrātcentimetri"} |
| - per{"uz kvadrātcentimetru"} |
| + per{"{0} uz kvadrātcentimetru"} |
| zero{"{0} kvadrātcentimetri"} |
| } |
| square-foot{ |
| @@ -103,7 +103,7 @@ lv{ |
| dnam{"kvadrātmetri"} |
| one{"{0} kvadrātmetrs"} |
| other{"{0} kvadrātmetri"} |
| - per{"uz kvadrātmetru"} |
| + per{"{0} uz kvadrātmetru"} |
| zero{"{0} kvadrātmetri"} |
| } |
| square-mile{ |
| @@ -704,7 +704,7 @@ lv{ |
| dnam{"kubikcentimetri"} |
| one{"{0} kubikcentimetrs"} |
| other{"{0} kubikcentimetri"} |
| - per{"uz kubikcentimetru"} |
| + per{"{0} uz kubikcentimetru"} |
| zero{"{0} kubikcentimetri"} |
| } |
| cubic-foot{ |
| @@ -729,7 +729,7 @@ lv{ |
| dnam{"kubikmetri"} |
| one{"{0} kubikmetrs"} |
| other{"{0} kubikmetri"} |
| - per{"uz kubikmetru"} |
| + per{"{0} uz kubikmetru"} |
| zero{"{0} kubikmetri"} |
| } |
| cubic-mile{ |
| @@ -779,7 +779,7 @@ lv{ |
| dnam{"litri"} |
| one{"{0} litrs"} |
| other{"{0} litri"} |
| - per{"uz litru"} |
| + per{"{0} uz litru"} |
| zero{"{0} litri"} |
| } |
| megaliter{ |
| diff --git a/source/data/unit/my.txt b/source/data/unit/my.txt |
| index 0a24ab4..07b5dcc 100755 |
| --- a/source/data/unit/my.txt |
| +++ b/source/data/unit/my.txt |
| @@ -386,7 +386,7 @@ my{ |
| pound{ |
| dnam{"ပေါင်"} |
| other{"{0}ပေါင်"} |
| - per{"တစ်ပေါင်လျှင်"} |
| + per{"{0}တစ်ပေါင်လျှင်"} |
| } |
| ton{ |
| dnam{"တန်"} |
| @@ -512,7 +512,7 @@ my{ |
| cubic-meter{ |
| dnam{"ကုဗမီတာ"} |
| other{"{0}ကုဗမီတာ"} |
| - per{"တစ်ကုဗစင်တီမီတာလျှင်"} |
| + per{"{0}တစ်ကုဗမီတာလျှင်"} |
| } |
| cubic-mile{ |
| dnam{"ကုဗမိုင်"} |
| diff --git a/source/data/unit/pt_PT.txt b/source/data/unit/pt_PT.txt |
| index a3b82c7..3108255 100755 |
| --- a/source/data/unit/pt_PT.txt |
| +++ b/source/data/unit/pt_PT.txt |
| @@ -110,7 +110,7 @@ pt_PT{ |
| } |
| volume{ |
| cubic-centimeter{ |
| - per{"por centímetro cúbico"} |
| + per{"{0} por centímetro cúbico"} |
| } |
| cubic-kilometer{ |
| dnam{"quilómetros cúbicos"} |
| diff --git a/source/data/unit/ro.txt b/source/data/unit/ro.txt |
| index a31e161..e74eaa8 100755 |
| --- a/source/data/unit/ro.txt |
| +++ b/source/data/unit/ro.txt |
| @@ -504,7 +504,7 @@ ro{ |
| few{"{0} grame"} |
| one{"{0} gram"} |
| other{"{0} de grame"} |
| - per{"per gram"} |
| + per{"{0} per gram"} |
| } |
| kilogram{ |
| dnam{"kilograme"} |
| diff --git a/source/data/unit/si.txt b/source/data/unit/si.txt |
| index 1f4da6b..47fae31 100755 |
| --- a/source/data/unit/si.txt |
| +++ b/source/data/unit/si.txt |
| @@ -230,7 +230,7 @@ si{ |
| dnam{"තත්පර"} |
| one{"තත්පර {0}"} |
| other{"තත්පර {0}"} |
| - per{"තත්පරයට"} |
| + per{"තත්පරයට {0}"} |
| } |
| week{ |
| dnam{"සති"} |
| diff --git a/source/data/unit/sr.txt b/source/data/unit/sr.txt |
| index a00f8aa..2d09960 100755 |
| --- a/source/data/unit/sr.txt |
| +++ b/source/data/unit/sr.txt |
| @@ -772,7 +772,7 @@ sr{ |
| few{"{0} литра"} |
| one{"{0} литар"} |
| other{"{0} литара"} |
| - per{"по литри"} |
| + per{"{0} по литри"} |
| } |
| megaliter{ |
| dnam{"мегалитри"} |
| diff --git a/source/data/unit/sr_Latn.txt b/source/data/unit/sr_Latn.txt |
| index 82830ae..0fe7b9e 100755 |
| --- a/source/data/unit/sr_Latn.txt |
| +++ b/source/data/unit/sr_Latn.txt |
| @@ -773,7 +773,7 @@ sr_Latn{ |
| few{"{0} litra"} |
| one{"{0} litar"} |
| other{"{0} litara"} |
| - per{"po litri"} |
| + per{"{0} po litri"} |
| } |
| megaliter{ |
| dnam{"megalitri"} |
| diff --git a/source/data/unit/tr.txt b/source/data/unit/tr.txt |
| index 8f5c1f0..39411a9 100755 |
| --- a/source/data/unit/tr.txt |
| +++ b/source/data/unit/tr.txt |
| @@ -68,7 +68,7 @@ tr{ |
| dnam{"santimetrekare"} |
| one{"{0} santimetrekare"} |
| other{"{0} santimetrekare"} |
| - per{"/santimetrekare"} |
| + per{"{0}/santimetrekare"} |
| } |
| square-foot{ |
| dnam{"fit kare"} |
| @@ -90,7 +90,7 @@ tr{ |
| dnam{"metrekare"} |
| one{"{0} metrekare"} |
| other{"{0} metrekare"} |
| - per{"/metrekare"} |
| + per{"{0}/metrekare"} |
| } |
| square-mile{ |
| dnam{"mil kare"} |
| @@ -642,7 +642,7 @@ tr{ |
| dnam{"metreküp"} |
| one{"{0} metreküp"} |
| other{"{0} metreküp"} |
| - per{"/metreküp"} |
| + per{"{0}/metreküp"} |
| } |
| cubic-mile{ |
| dnam{"mil küp"} |
| diff --git a/source/i18n/Makefile.in b/source/i18n/Makefile.in |
| index 53dd5fd..b59b2ce 100644 |
| --- a/source/i18n/Makefile.in |
| +++ b/source/i18n/Makefile.in |
| @@ -88,7 +88,7 @@ regexcmp.o rematch.o repattrn.o regexst.o regextxt.o regeximp.o uregex.o uregexc |
| ulocdata.o measfmt.o currfmt.o curramt.o currunit.o measure.o utmscale.o \ |
| csdetect.o csmatch.o csr2022.o csrecog.o csrmbcs.o csrsbcs.o csrucode.o csrutf8.o inputext.o \ |
| wintzimpl.o windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o zonemeta.o \ |
| -upluralrules.o plurrule.o plurfmt.o selfmt.o dtitvfmt.o dtitvinf.o udateintervalformat.o \ |
| +standardplural.o upluralrules.o plurrule.o plurfmt.o selfmt.o dtitvfmt.o dtitvinf.o udateintervalformat.o \ |
| tmunit.o tmutamt.o tmutfmt.o currpinf.o \ |
| uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o uspoof_wsconf.o decfmtst.o smpdtfst.o \ |
| ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o ufieldpositer.o locdspnm.o \ |
| diff --git a/source/i18n/i18n.vcxproj b/source/i18n/i18n.vcxproj |
| index 8c586a9..b036d95 100644 |
| --- a/source/i18n/i18n.vcxproj |
| +++ b/source/i18n/i18n.vcxproj |
| @@ -364,6 +364,7 @@ |
| <ClCompile Include="scriptset.cpp" /> |
| <ClCompile Include="smpdtfmt.cpp" /> |
| <ClCompile Include="smpdtfst.cpp" /> |
| + <ClCompile Include="standardplural.cpp" /> |
| <ClCompile Include="taiwncal.cpp" /> |
| <ClCompile Include="timezone.cpp" /> |
| <ClCompile Include="tmunit.cpp" /> |
| @@ -1278,6 +1279,7 @@ |
| <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs> |
| </CustomBuild> |
| <ClInclude Include="smpdtfst.h" /> |
| + <ClInclude Include="standardplural.h" /> |
| <ClInclude Include="taiwncal.h" /> |
| <CustomBuild Include="unicode\timezone.h"> |
| <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">copy "%(FullPath)" ..\..\include\unicode |
| diff --git a/source/i18n/i18n.vcxproj.filters b/source/i18n/i18n.vcxproj.filters |
| index 35c3fe2..9164f5a 100644 |
| --- a/source/i18n/i18n.vcxproj.filters |
| +++ b/source/i18n/i18n.vcxproj.filters |
| @@ -282,6 +282,9 @@ |
| <ClCompile Include="smpdtfst.cpp"> |
| <Filter>formatting</Filter> |
| </ClCompile> |
| + <ClCompile Include="standardplural.cpp"> |
| + <Filter>formatting</Filter> |
| + </ClCompile> |
| <ClCompile Include="taiwncal.cpp"> |
| <Filter>formatting</Filter> |
| </ClCompile> |
| @@ -787,6 +790,9 @@ |
| <ClInclude Include="smpdtfst.h"> |
| <Filter>formatting</Filter> |
| </ClInclude> |
| + <ClInclude Include="standardplural.h"> |
| + <Filter>formatting</Filter> |
| + </ClInclude> |
| <ClInclude Include="taiwncal.h"> |
| <Filter>formatting</Filter> |
| </ClInclude> |
| diff --git a/source/i18n/measfmt.cpp b/source/i18n/measfmt.cpp |
| index 6f8851c..1af72e9 100644 |
| --- a/source/i18n/measfmt.cpp |
| +++ b/source/i18n/measfmt.cpp |
| @@ -17,6 +17,7 @@ |
| #include "unicode/numfmt.h" |
| #include "currfmt.h" |
| #include "unicode/localpointer.h" |
| +#include "resource.h" |
| #include "simplepatternformatter.h" |
| #include "quantityformatter.h" |
| #include "unicode/plurrule.h" |
| @@ -35,6 +36,7 @@ |
| |
| #include "sharednumberformat.h" |
| #include "sharedpluralrules.h" |
| +#include "standardplural.h" |
| #include "unifiedcache.h" |
| |
| #define MEAS_UNIT_COUNT 129 |
| @@ -76,20 +78,53 @@ private: |
| NumericDateFormatters &operator=(const NumericDateFormatters &other); |
| }; |
| |
| -// Instances contain all MeasureFormat specific data for a particular locale. |
| -// This data is cached. It is never copied, but is shared via shared pointers. |
| +static UMeasureFormatWidth getRegularWidth(UMeasureFormatWidth width) { |
| + if (width >= WIDTH_INDEX_COUNT) { |
| + return UMEASFMT_WIDTH_NARROW; |
| + } |
| + return width; |
| +} |
| + |
| +/** |
| + * Instances contain all MeasureFormat specific data for a particular locale. |
| + * This data is cached. It is never copied, but is shared via shared pointers. |
| + * |
| + * Note: We might change the cache data to have an array[WIDTH_INDEX_COUNT] of |
| + * complete sets of unit & per patterns, |
| + * to correspond to the resource data and its aliases. |
| + * |
| + * TODO: Maybe store more sparsely in general, with pointers rather than potentially-empty objects. |
| + */ |
| class MeasureFormatCacheData : public SharedObject { |
| public: |
| - QuantityFormatter formatters[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT]; |
| + static const int32_t PER_UNIT_INDEX = StandardPlural::COUNT; |
| + static const int32_t PATTERN_COUNT = PER_UNIT_INDEX + 1; |
| + |
| + /** |
| + * Redirection data from root-bundle, top-level sideways aliases. |
| + * - UMEASFMT_WIDTH_COUNT: initial value, just fall back to root |
| + * - UMEASFMT_WIDTH_WIDE/SHORT/NARROW: sideways alias for missing data |
| + */ |
| + UMeasureFormatWidth widthFallback[WIDTH_INDEX_COUNT]; |
| + /** Measure unit -> format width -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX) */ |
| + SimplePatternFormatter *patterns[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT][PATTERN_COUNT]; |
| SimplePatternFormatter perFormatters[WIDTH_INDEX_COUNT]; |
| |
| MeasureFormatCacheData(); |
| + virtual ~MeasureFormatCacheData(); |
| + |
| + UBool hasPerFormatter(int32_t width) const { |
| + // TODO: Create a more obvious way to test if the per-formatter has been set? |
| + // Use pointers, check for NULL? Or add an isValid() method? |
| + return perFormatters[width].getPlaceholderCount() == 2; |
| + } |
| + |
| void adoptCurrencyFormat(int32_t widthIndex, NumberFormat *nfToAdopt) { |
| delete currencyFormats[widthIndex]; |
| currencyFormats[widthIndex] = nfToAdopt; |
| } |
| - const NumberFormat *getCurrencyFormat(int32_t widthIndex) const { |
| - return currencyFormats[widthIndex]; |
| + const NumberFormat *getCurrencyFormat(UMeasureFormatWidth width) const { |
| + return currencyFormats[getRegularWidth(width)]; |
| } |
| void adoptIntegerFormat(NumberFormat *nfToAdopt) { |
| delete integerFormat; |
| @@ -105,36 +140,23 @@ public: |
| const NumericDateFormatters *getNumericDateFormatters() const { |
| return numericDateFormatters; |
| } |
| - void adoptPerUnitFormatter( |
| - int32_t index, |
| - int32_t widthIndex, |
| - SimplePatternFormatter *formatterToAdopt) { |
| - delete perUnitFormatters[index][widthIndex]; |
| - perUnitFormatters[index][widthIndex] = formatterToAdopt; |
| - } |
| - const SimplePatternFormatter * const * getPerUnitFormattersByIndex( |
| - int32_t index) const { |
| - return perUnitFormatters[index]; |
| - } |
| - virtual ~MeasureFormatCacheData(); |
| + |
| private: |
| NumberFormat *currencyFormats[WIDTH_INDEX_COUNT]; |
| NumberFormat *integerFormat; |
| NumericDateFormatters *numericDateFormatters; |
| - SimplePatternFormatter *perUnitFormatters[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT]; |
| MeasureFormatCacheData(const MeasureFormatCacheData &other); |
| MeasureFormatCacheData &operator=(const MeasureFormatCacheData &other); |
| }; |
| |
| MeasureFormatCacheData::MeasureFormatCacheData() { |
| + for (int32_t i = 0; i < WIDTH_INDEX_COUNT; ++i) { |
| + widthFallback[i] = UMEASFMT_WIDTH_COUNT; |
| + } |
| for (int32_t i = 0; i < UPRV_LENGTHOF(currencyFormats); ++i) { |
| currencyFormats[i] = NULL; |
| } |
| - for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) { |
| - for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) { |
| - perUnitFormatters[i][j] = NULL; |
| - } |
| - } |
| + uprv_memset(patterns, 0, sizeof(patterns)); |
| integerFormat = NULL; |
| numericDateFormatters = NULL; |
| } |
| @@ -145,20 +167,15 @@ MeasureFormatCacheData::~MeasureFormatCacheData() { |
| } |
| for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) { |
| for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) { |
| - delete perUnitFormatters[i][j]; |
| + for (int32_t k = 0; k < PATTERN_COUNT; ++k) { |
| + delete patterns[i][j][k]; |
| + } |
| } |
| } |
| delete integerFormat; |
| delete numericDateFormatters; |
| } |
| |
| -static int32_t widthToIndex(UMeasureFormatWidth width) { |
| - if (width >= WIDTH_INDEX_COUNT) { |
| - return WIDTH_INDEX_COUNT - 1; |
| - } |
| - return width; |
| -} |
| - |
| static UBool isCurrency(const MeasureUnit &unit) { |
| return (uprv_strcmp(unit.getType(), "currency") == 0); |
| } |
| @@ -176,118 +193,219 @@ static UBool getString( |
| return TRUE; |
| } |
| |
| +namespace { |
| |
| -static UBool loadMeasureUnitData( |
| - const UResourceBundle *resource, |
| - MeasureFormatCacheData &cacheData, |
| - UErrorCode &status) { |
| - if (U_FAILURE(status)) { |
| - return FALSE; |
| - } |
| - static const char *widthPath[] = {"units", "unitsShort", "unitsNarrow"}; |
| - MeasureUnit *units = NULL; |
| - int32_t unitCount = MeasureUnit::getAvailable(units, 0, status); |
| - while (status == U_BUFFER_OVERFLOW_ERROR) { |
| - status = U_ZERO_ERROR; |
| - delete [] units; |
| - units = new MeasureUnit[unitCount]; |
| - if (units == NULL) { |
| - status = U_MEMORY_ALLOCATION_ERROR; |
| - return FALSE; |
| - } |
| - unitCount = MeasureUnit::getAvailable(units, unitCount, status); |
| - } |
| - for (int32_t currentWidth = 0; currentWidth < WIDTH_INDEX_COUNT; ++currentWidth) { |
| - // Be sure status is clear since next resource bundle lookup may fail. |
| - if (U_FAILURE(status)) { |
| - delete [] units; |
| - return FALSE; |
| - } |
| - LocalUResourceBundlePointer widthBundle( |
| - ures_getByKeyWithFallback( |
| - resource, widthPath[currentWidth], NULL, &status)); |
| - // We may not have data for all widths in all locales. |
| - if (status == U_MISSING_RESOURCE_ERROR) { |
| - status = U_ZERO_ERROR; |
| - continue; |
| +static const UChar g_LOCALE_units[] = { |
| + 0x2F, 0x4C, 0x4F, 0x43, 0x41, 0x4C, 0x45, 0x2F, |
| + 0x75, 0x6E, 0x69, 0x74, 0x73 |
| +}; |
| +static const UChar gShort[] = { 0x53, 0x68, 0x6F, 0x72, 0x74 }; |
| +static const UChar gNarrow[] = { 0x4E, 0x61, 0x72, 0x72, 0x6F, 0x77 }; |
| + |
| +/** |
| + * Sink for enumerating all of the measurement unit display names. |
| + * Contains inner sink classes, each one corresponding to a type of resource table. |
| + * The outer sink handles the top-level units, unitsNarrow, and unitsShort tables. |
| + * |
| + * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): |
| + * Only store a value if it is still missing, that is, it has not been overridden. |
| + * |
| + * C++: Each inner sink class has a reference to the main outer sink. |
| + * Java: Use non-static inner classes instead. |
| + */ |
| +struct UnitDataSink : public ResourceTableSink { |
| + /** |
| + * Sink for a table of display patterns. For example, |
| + * unitsShort/duration/hour contains other{"{0} hrs"}. |
| + */ |
| + struct UnitPatternSink : public ResourceTableSink { |
| + UnitPatternSink(UnitDataSink &sink) : outer(sink) {} |
| + ~UnitPatternSink(); |
| + |
| + void setFormatterIfAbsent(int32_t index, const ResourceValue &value, |
| + int32_t minPlaceholders, UErrorCode &errorCode) { |
| + SimplePatternFormatter **patterns = |
| + &outer.cacheData.patterns[outer.unitIndex][outer.width][0]; |
| + if (U_SUCCESS(errorCode) && patterns[index] == NULL) { |
| + patterns[index] = new SimplePatternFormatter( |
| + value.getUnicodeString(errorCode), minPlaceholders, 1, errorCode); |
| + if (U_SUCCESS(errorCode) && patterns[index] == NULL) { |
| + errorCode = U_MEMORY_ALLOCATION_ERROR; |
| + } |
| + } |
| } |
| - { |
| - // compound per |
| - LocalUResourceBundlePointer compoundPerBundle( |
| - ures_getByKeyWithFallback( |
| - widthBundle.getAlias(), |
| - "compound/per", |
| - NULL, |
| - &status)); |
| - if (U_FAILURE(status)) { |
| - status = U_ZERO_ERROR; |
| + |
| + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return; } |
| + if (uprv_strcmp(key, "dnam") == 0) { |
| + // Skip the unit display name for now. |
| + } else if (uprv_strcmp(key, "per") == 0) { |
| + // For example, "{0}/h". |
| + setFormatterIfAbsent(MeasureFormatCacheData::PER_UNIT_INDEX, value, 1, errorCode); |
| } else { |
| - UnicodeString perPattern; |
| - getString(compoundPerBundle.getAlias(), perPattern, status); |
| - cacheData.perFormatters[currentWidth].compile(perPattern, status); |
| + // The key must be one of the plural form strings. For example: |
| + // one{"{0} hr"} |
| + // other{"{0} hrs"} |
| + setFormatterIfAbsent(StandardPlural::indexFromString(key, errorCode), value, 0, |
| + errorCode); |
| } |
| } |
| - for (int32_t currentUnit = 0; currentUnit < unitCount; ++currentUnit) { |
| - // Be sure status is clear next lookup may fail. |
| - if (U_FAILURE(status)) { |
| - delete [] units; |
| - return FALSE; |
| + UnitDataSink &outer; |
| + } patternSink; |
| + |
| + /** |
| + * Sink for a table of per-unit tables. For example, |
| + * unitsShort/duration contains tables for duration-unit subtypes day & hour. |
| + */ |
| + struct UnitSubtypeSink : public ResourceTableSink { |
| + UnitSubtypeSink(UnitDataSink &sink) : outer(sink) {} |
| + ~UnitSubtypeSink(); |
| + virtual ResourceTableSink *getOrCreateTableSink( |
| + const char *key, int32_t /* initialSize */, UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return NULL; } |
| + outer.unitIndex = MeasureUnit::internalGetIndexForTypeAndSubtype(outer.type, key); |
| + if (outer.unitIndex >= 0) { |
| + return &outer.patternSink; |
| } |
| - if (isCurrency(units[currentUnit])) { |
| - continue; |
| + return NULL; |
| + } |
| + UnitDataSink &outer; |
| + } subtypeSink; |
| + |
| + /** |
| + * Sink for compound x-per-y display pattern. For example, |
| + * unitsShort/compound/per may be "{0}/{1}". |
| + */ |
| + struct UnitCompoundSink : public ResourceTableSink { |
| + UnitCompoundSink(UnitDataSink &sink) : outer(sink) {} |
| + ~UnitCompoundSink(); |
| + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
| + if (U_SUCCESS(errorCode) && uprv_strcmp(key, "per") == 0) { |
| + outer.cacheData.perFormatters[outer.width]. |
| + compileMinMaxPlaceholders(value.getUnicodeString(errorCode), 2, 2, errorCode); |
| } |
| - CharString pathBuffer; |
| - pathBuffer.append(units[currentUnit].getType(), status) |
| - .append("/", status) |
| - .append(units[currentUnit].getSubtype(), status); |
| - LocalUResourceBundlePointer unitBundle( |
| - ures_getByKeyWithFallback( |
| - widthBundle.getAlias(), |
| - pathBuffer.data(), |
| - NULL, |
| - &status)); |
| - // We may not have data for all units in all widths |
| - if (status == U_MISSING_RESOURCE_ERROR) { |
| - status = U_ZERO_ERROR; |
| - continue; |
| + } |
| + UnitDataSink &outer; |
| + } compoundSink; |
| + |
| + /** |
| + * Sink for a table of unit type tables. For example, |
| + * unitsShort contains tables for area & duration. |
| + * It also contains a table for the compound/per pattern. |
| + */ |
| + struct UnitTypeSink : public ResourceTableSink { |
| + UnitTypeSink(UnitDataSink &sink) : outer(sink) {} |
| + ~UnitTypeSink(); |
| + virtual ResourceTableSink *getOrCreateTableSink( |
| + const char *key, int32_t /* initialSize */, UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return NULL; } |
| + if (uprv_strcmp(key, "currency") == 0) { |
| + // Skip. |
| + } else if (uprv_strcmp(key, "compound") == 0) { |
| + if (!outer.cacheData.hasPerFormatter(outer.width)) { |
| + return &outer.compoundSink; |
| + } |
| + } else { |
| + outer.type = key; |
| + return &outer.subtypeSink; |
| } |
| - // We must have the unit bundle to proceed |
| - if (U_FAILURE(status)) { |
| - delete [] units; |
| - return FALSE; |
| + return NULL; |
| + } |
| + UnitDataSink &outer; |
| + } typeSink; |
| + |
| + UnitDataSink(MeasureFormatCacheData &outputData) |
| + : patternSink(*this), subtypeSink(*this), compoundSink(*this), typeSink(*this), |
| + cacheData(outputData), |
| + width(UMEASFMT_WIDTH_COUNT), type(NULL), unitIndex(0) {} |
| + ~UnitDataSink(); |
| + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
| + // Handle aliases like |
| + // units:alias{"/LOCALE/unitsShort"} |
| + // which should only occur in the root bundle. |
| + if (U_FAILURE(errorCode) || value.getType() != URES_ALIAS) { return; } |
| + UMeasureFormatWidth sourceWidth = widthFromKey(key); |
| + if (sourceWidth == UMEASFMT_WIDTH_COUNT) { |
| + // Alias from something we don't care about. |
| + return; |
| + } |
| + UMeasureFormatWidth targetWidth = widthFromAlias(value, errorCode); |
| + if (targetWidth == UMEASFMT_WIDTH_COUNT) { |
| + // We do not recognize what to fall back to. |
| + errorCode = U_INVALID_FORMAT_ERROR; |
| + return; |
| + } |
| + // Check that we do not fall back to another fallback. |
| + if (cacheData.widthFallback[targetWidth] != UMEASFMT_WIDTH_COUNT) { |
| + errorCode = U_INVALID_FORMAT_ERROR; |
| + return; |
| + } |
| + cacheData.widthFallback[sourceWidth] = targetWidth; |
| + } |
| + virtual ResourceTableSink *getOrCreateTableSink( |
| + const char *key, int32_t /* initialSize */, UErrorCode &errorCode) { |
| + if (U_SUCCESS(errorCode) && (width = widthFromKey(key)) != UMEASFMT_WIDTH_COUNT) { |
| + return &typeSink; |
| + } |
| + return NULL; |
| + } |
| + |
| + static UMeasureFormatWidth widthFromKey(const char *key) { |
| + if (uprv_strncmp(key, "units", 5) == 0) { |
| + key += 5; |
| + if (*key == 0) { |
| + return UMEASFMT_WIDTH_WIDE; |
| + } else if (uprv_strcmp(key, "Short") == 0) { |
| + return UMEASFMT_WIDTH_SHORT; |
| + } else if (uprv_strcmp(key, "Narrow") == 0) { |
| + return UMEASFMT_WIDTH_NARROW; |
| } |
| - int32_t size = ures_getSize(unitBundle.getAlias()); |
| - for (int32_t plIndex = 0; plIndex < size; ++plIndex) { |
| - LocalUResourceBundlePointer pluralBundle( |
| - ures_getByIndex( |
| - unitBundle.getAlias(), plIndex, NULL, &status)); |
| - if (U_FAILURE(status)) { |
| - delete [] units; |
| - return FALSE; |
| - } |
| - const char * resKey = ures_getKey(pluralBundle.getAlias()); |
| - if (uprv_strcmp(resKey, "dnam") == 0) { |
| - continue; // skip display name & per pattern (new in CLDR 26 / ICU 54) for now, not part of plurals |
| - } |
| - if (uprv_strcmp(resKey, "per") == 0) { |
| - UnicodeString perPattern; |
| - getString(pluralBundle.getAlias(), perPattern, status); |
| - cacheData.adoptPerUnitFormatter( |
| - units[currentUnit].getIndex(), |
| - currentWidth, |
| - new SimplePatternFormatter(perPattern)); |
| - continue; |
| - } |
| - UnicodeString rawPattern; |
| - getString(pluralBundle.getAlias(), rawPattern, status); |
| - cacheData.formatters[units[currentUnit].getIndex()][currentWidth].add( |
| - resKey, |
| - rawPattern, |
| - status); |
| + } |
| + return UMEASFMT_WIDTH_COUNT; |
| + } |
| + |
| + static UMeasureFormatWidth widthFromAlias(const ResourceValue &value, UErrorCode &errorCode) { |
| + int32_t length; |
| + const UChar *s = value.getAliasString(length, errorCode); |
| + // For example: "/LOCALE/unitsShort" |
| + if (U_SUCCESS(errorCode) && length >= 13 && u_memcmp(s, g_LOCALE_units, 13) == 0) { |
| + s += 13; |
| + length -= 13; |
| + if (*s == 0) { |
| + return UMEASFMT_WIDTH_WIDE; |
| + } else if (u_strCompare(s, length, gShort, 5, FALSE) == 0) { |
| + return UMEASFMT_WIDTH_SHORT; |
| + } else if (u_strCompare(s, length, gNarrow, 6, FALSE) == 0) { |
| + return UMEASFMT_WIDTH_NARROW; |
| } |
| } |
| + return UMEASFMT_WIDTH_COUNT; |
| } |
| - delete [] units; |
| + |
| + // Output data. |
| + MeasureFormatCacheData &cacheData; |
| + |
| + // Path to current data. |
| + UMeasureFormatWidth width; |
| + const char *type; |
| + int32_t unitIndex; |
| +}; |
| + |
| +// Virtual destructors must be defined out of line. |
| +UnitDataSink::UnitPatternSink::~UnitPatternSink() {} |
| +UnitDataSink::UnitSubtypeSink::~UnitSubtypeSink() {} |
| +UnitDataSink::UnitCompoundSink::~UnitCompoundSink() {} |
| +UnitDataSink::UnitTypeSink::~UnitTypeSink() {} |
| +UnitDataSink::~UnitDataSink() {} |
| + |
| +} // namespace |
| + |
| +static UBool loadMeasureUnitData( |
| + const UResourceBundle *resource, |
| + MeasureFormatCacheData &cacheData, |
| + UErrorCode &status) { |
| + UnitDataSink sink(cacheData); |
| + ures_getAllTableItemsWithFallback(resource, "", sink, status); |
| return U_SUCCESS(status); |
| } |
| |
| @@ -486,7 +604,9 @@ MeasureFormat::MeasureFormat(const MeasureFormat &other) : |
| cache->addRef(); |
| numberFormat->addRef(); |
| pluralRules->addRef(); |
| - listFormatter = new ListFormatter(*other.listFormatter); |
| + if (other.listFormatter != NULL) { |
| + listFormatter = new ListFormatter(*other.listFormatter); |
| + } |
| } |
| |
| MeasureFormat &MeasureFormat::operator=(const MeasureFormat &other) { |
| @@ -499,7 +619,11 @@ MeasureFormat &MeasureFormat::operator=(const MeasureFormat &other) { |
| SharedObject::copyPtr(other.pluralRules, pluralRules); |
| width = other.width; |
| delete listFormatter; |
| - listFormatter = new ListFormatter(*other.listFormatter); |
| + if (other.listFormatter != NULL) { |
| + listFormatter = new ListFormatter(*other.listFormatter); |
| + } else { |
| + listFormatter = NULL; |
| + } |
| return *this; |
| } |
| |
| @@ -714,7 +838,7 @@ void MeasureFormat::initMeasureFormat( |
| delete listFormatter; |
| listFormatter = ListFormatter::createInstance( |
| locale, |
| - listStyles[widthToIndex(width)], |
| + listStyles[getRegularWidth(width)], |
| status); |
| } |
| |
| @@ -771,24 +895,17 @@ UnicodeString &MeasureFormat::formatMeasure( |
| if (isCurrency(amtUnit)) { |
| UChar isoCode[4]; |
| u_charsToUChars(amtUnit.getSubtype(), isoCode, 4); |
| - return cache->getCurrencyFormat(widthToIndex(width))->format( |
| + return cache->getCurrencyFormat(width)->format( |
| new CurrencyAmount(amtNumber, isoCode, status), |
| appendTo, |
| pos, |
| status); |
| } |
| - const QuantityFormatter *quantityFormatter = getQuantityFormatter( |
| - amtUnit.getIndex(), widthToIndex(width), status); |
| - if (U_FAILURE(status)) { |
| - return appendTo; |
| - } |
| - return quantityFormatter->format( |
| - amtNumber, |
| - nf, |
| - **pluralRules, |
| - appendTo, |
| - pos, |
| - status); |
| + UnicodeString formattedNumber; |
| + StandardPlural::Form pluralForm = QuantityFormatter::selectPlural( |
| + amtNumber, nf, **pluralRules, formattedNumber, pos, status); |
| + const SimplePatternFormatter *formatter = getPluralFormatter(amtUnit, width, pluralForm, status); |
| + return QuantityFormatter::format(*formatter, formattedNumber, appendTo, pos, status); |
| } |
| |
| // Formats hours-minutes-seconds as 5:37:23 or similar. |
| @@ -920,64 +1037,69 @@ UnicodeString &MeasureFormat::formatNumeric( |
| return appendTo; |
| } |
| |
| -const QuantityFormatter *MeasureFormat::getQuantityFormatter( |
| - int32_t index, |
| - int32_t widthIndex, |
| - UErrorCode &status) const { |
| - if (U_FAILURE(status)) { |
| - return NULL; |
| +const SimplePatternFormatter *MeasureFormat::getFormatterOrNull( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const { |
| + width = getRegularWidth(width); |
| + SimplePatternFormatter *const (*unitPatterns)[MeasureFormatCacheData::PATTERN_COUNT] = |
| + &cache->patterns[unit.getIndex()][0]; |
| + if (unitPatterns[width][index] != NULL) { |
| + return unitPatterns[width][index]; |
| } |
| - const QuantityFormatter *formatters = |
| - cache->formatters[index]; |
| - if (formatters[widthIndex].isValid()) { |
| - return &formatters[widthIndex]; |
| + int32_t fallbackWidth = cache->widthFallback[width]; |
| + if (fallbackWidth != UMEASFMT_WIDTH_COUNT && unitPatterns[fallbackWidth][index] != NULL) { |
| + return unitPatterns[fallbackWidth][index]; |
| } |
| - if (formatters[UMEASFMT_WIDTH_SHORT].isValid()) { |
| - return &formatters[UMEASFMT_WIDTH_SHORT]; |
| - } |
| - status = U_MISSING_RESOURCE_ERROR; |
| return NULL; |
| } |
| |
| -const SimplePatternFormatter *MeasureFormat::getPerUnitFormatter( |
| - int32_t index, |
| - int32_t widthIndex) const { |
| - const SimplePatternFormatter * const * perUnitFormatters = |
| - cache->getPerUnitFormattersByIndex(index); |
| - if (perUnitFormatters[widthIndex] != NULL) { |
| - return perUnitFormatters[widthIndex]; |
| +const SimplePatternFormatter *MeasureFormat::getFormatter( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
| + UErrorCode &errorCode) const { |
| + if (U_FAILURE(errorCode)) { |
| + return NULL; |
| } |
| - if (perUnitFormatters[UMEASFMT_WIDTH_SHORT] != NULL) { |
| - return perUnitFormatters[UMEASFMT_WIDTH_SHORT]; |
| + const SimplePatternFormatter *pattern = getFormatterOrNull(unit, width, index); |
| + if (pattern == NULL) { |
| + errorCode = U_MISSING_RESOURCE_ERROR; |
| } |
| - return NULL; |
| + return pattern; |
| +} |
| + |
| +const SimplePatternFormatter *MeasureFormat::getPluralFormatter( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
| + UErrorCode &errorCode) const { |
| + if (U_FAILURE(errorCode)) { |
| + return NULL; |
| + } |
| + if (index != StandardPlural::OTHER) { |
| + const SimplePatternFormatter *pattern = getFormatterOrNull(unit, width, index); |
| + if (pattern != NULL) { |
| + return pattern; |
| + } |
| + } |
| + return getFormatter(unit, width, StandardPlural::OTHER, errorCode); |
| } |
| |
| const SimplePatternFormatter *MeasureFormat::getPerFormatter( |
| - int32_t widthIndex, |
| + UMeasureFormatWidth width, |
| UErrorCode &status) const { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| + width = getRegularWidth(width); |
| const SimplePatternFormatter * perFormatters = cache->perFormatters; |
| - |
| - if (perFormatters[widthIndex].getPlaceholderCount() == 2) { |
| - return &perFormatters[widthIndex]; |
| + if (perFormatters[width].getPlaceholderCount() == 2) { |
| + return &perFormatters[width]; |
| } |
| - if (perFormatters[UMEASFMT_WIDTH_SHORT].getPlaceholderCount() == 2) { |
| - return &perFormatters[UMEASFMT_WIDTH_SHORT]; |
| + int32_t fallbackWidth = cache->widthFallback[width]; |
| + if (fallbackWidth != UMEASFMT_WIDTH_COUNT && |
| + perFormatters[fallbackWidth].getPlaceholderCount() == 2) { |
| + return &perFormatters[fallbackWidth]; |
| } |
| status = U_MISSING_RESOURCE_ERROR; |
| return NULL; |
| } |
| |
| -static void getPerUnitString( |
| - const QuantityFormatter &formatter, |
| - UnicodeString &result) { |
| - result = formatter.getByVariant("one")->getPatternWithNoPlaceholders(); |
| - result.trim(); |
| -} |
| - |
| int32_t MeasureFormat::withPerUnitAndAppend( |
| const UnicodeString &formatted, |
| const MeasureUnit &perUnit, |
| @@ -987,8 +1109,8 @@ int32_t MeasureFormat::withPerUnitAndAppend( |
| if (U_FAILURE(status)) { |
| return offset; |
| } |
| - const SimplePatternFormatter *perUnitFormatter = getPerUnitFormatter( |
| - perUnit.getIndex(), widthToIndex(width)); |
| + const SimplePatternFormatter *perUnitFormatter = |
| + getFormatterOrNull(perUnit, width, MeasureFormatCacheData::PER_UNIT_INDEX); |
| if (perUnitFormatter != NULL) { |
| const UnicodeString *params[] = {&formatted}; |
| perUnitFormatter->formatAndAppend( |
| @@ -1000,15 +1122,14 @@ int32_t MeasureFormat::withPerUnitAndAppend( |
| status); |
| return offset; |
| } |
| - const SimplePatternFormatter *perFormatter = getPerFormatter( |
| - widthToIndex(width), status); |
| - const QuantityFormatter *qf = getQuantityFormatter( |
| - perUnit.getIndex(), widthToIndex(width), status); |
| + const SimplePatternFormatter *perFormatter = getPerFormatter(width, status); |
| + const SimplePatternFormatter *pattern = |
| + getPluralFormatter(perUnit, width, StandardPlural::ONE, status); |
| if (U_FAILURE(status)) { |
| return offset; |
| } |
| - UnicodeString perUnitString; |
| - getPerUnitString(*qf, perUnitString); |
| + UnicodeString perUnitString = pattern->getPatternWithNoPlaceholders(); |
| + perUnitString.trim(); |
| const UnicodeString *params[] = {&formatted, &perUnitString}; |
| perFormatter->formatAndAppend( |
| params, |
| diff --git a/source/i18n/measunit.cpp b/source/i18n/measunit.cpp |
| index 35a56df..40b9547 100644 |
| --- a/source/i18n/measunit.cpp |
| +++ b/source/i18n/measunit.cpp |
| @@ -1152,6 +1152,18 @@ int32_t MeasureUnit::getIndexCount() { |
| return gIndexes[UPRV_LENGTHOF(gIndexes) - 1]; |
| } |
| |
| +int32_t MeasureUnit::internalGetIndexForTypeAndSubtype(const char *type, const char *subtype) { |
| + int32_t t = binarySearch(gTypes, 0, UPRV_LENGTHOF(gTypes), type); |
| + if (t < 0) { |
| + return t; |
| + } |
| + int32_t st = binarySearch(gSubTypes, gOffsets[t], gOffsets[t + 1], subtype); |
| + if (st < 0) { |
| + return st; |
| + } |
| + return gIndexes[t] + st - gOffsets[t]; |
| +} |
| + |
| MeasureUnit *MeasureUnit::resolveUnitPerUnit( |
| const MeasureUnit &unit, const MeasureUnit &perUnit) { |
| int32_t unitOffset = unit.getOffset(); |
| diff --git a/source/i18n/plurrule_impl.h b/source/i18n/plurrule_impl.h |
| index 7416fa7..785600b 100644 |
| --- a/source/i18n/plurrule_impl.h |
| +++ b/source/i18n/plurrule_impl.h |
| @@ -10,18 +10,19 @@ |
| */ |
| |
| |
| -#ifndef PLURRULE_IMPLE |
| -#define PLURRULE_IMPLE |
| +#ifndef PLURRULE_IMPL |
| +#define PLURRULE_IMPL |
| |
| // Internal definitions for the PluralRules implementation. |
| |
| +#include "unicode/utypes.h" |
| + |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "unicode/format.h" |
| #include "unicode/locid.h" |
| #include "unicode/parseerr.h" |
| #include "unicode/ures.h" |
| -#include "unicode/utypes.h" |
| #include "uvector.h" |
| #include "hash.h" |
| |
| diff --git a/source/i18n/quantityformatter.cpp b/source/i18n/quantityformatter.cpp |
| index ed80b38..97c4c80 100644 |
| --- a/source/i18n/quantityformatter.cpp |
| +++ b/source/i18n/quantityformatter.cpp |
| @@ -5,6 +5,11 @@ |
| ****************************************************************************** |
| * quantityformatter.cpp |
| */ |
| + |
| +#include "unicode/utypes.h" |
| + |
| +#if !UCONFIG_NO_FORMATTING |
| + |
| #include "quantityformatter.h" |
| #include "simplepatternformatter.h" |
| #include "uassert.h" |
| @@ -15,26 +20,12 @@ |
| #include "charstr.h" |
| #include "unicode/fmtable.h" |
| #include "unicode/fieldpos.h" |
| +#include "standardplural.h" |
| #include "visibledigits.h" |
| - |
| -#if !UCONFIG_NO_FORMATTING |
| +#include "uassert.h" |
| |
| U_NAMESPACE_BEGIN |
| |
| -// other must always be first. |
| -static const char * const gPluralForms[] = { |
| - "other", "zero", "one", "two", "few", "many"}; |
| - |
| -static int32_t getPluralIndex(const char *pluralForm) { |
| - int32_t len = UPRV_LENGTHOF(gPluralForms); |
| - for (int32_t i = 0; i < len; ++i) { |
| - if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) { |
| - return i; |
| - } |
| - } |
| - return -1; |
| -} |
| - |
| QuantityFormatter::QuantityFormatter() { |
| for (int32_t i = 0; i < UPRV_LENGTHOF(formatters); ++i) { |
| formatters[i] = NULL; |
| @@ -80,112 +71,126 @@ void QuantityFormatter::reset() { |
| } |
| } |
| |
| -UBool QuantityFormatter::add( |
| +UBool QuantityFormatter::addIfAbsent( |
| const char *variant, |
| const UnicodeString &rawPattern, |
| UErrorCode &status) { |
| + int32_t pluralIndex = StandardPlural::indexFromString(variant, status); |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| - int32_t pluralIndex = getPluralIndex(variant); |
| - if (pluralIndex == -1) { |
| - status = U_ILLEGAL_ARGUMENT_ERROR; |
| - return FALSE; |
| + if (formatters[pluralIndex] != NULL) { |
| + return TRUE; |
| } |
| - SimplePatternFormatter *newFmt = |
| - new SimplePatternFormatter(rawPattern); |
| + SimplePatternFormatter *newFmt = new SimplePatternFormatter(rawPattern, 0, 1, status); |
| if (newFmt == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return FALSE; |
| } |
| - if (newFmt->getPlaceholderCount() > 1) { |
| + if (U_FAILURE(status)) { |
| delete newFmt; |
| - status = U_ILLEGAL_ARGUMENT_ERROR; |
| return FALSE; |
| } |
| - delete formatters[pluralIndex]; |
| formatters[pluralIndex] = newFmt; |
| return TRUE; |
| } |
| |
| UBool QuantityFormatter::isValid() const { |
| - return formatters[0] != NULL; |
| + return formatters[StandardPlural::OTHER] != NULL; |
| } |
| |
| const SimplePatternFormatter *QuantityFormatter::getByVariant( |
| const char *variant) const { |
| - int32_t pluralIndex = getPluralIndex(variant); |
| - if (pluralIndex == -1) { |
| - pluralIndex = 0; |
| - } |
| + U_ASSERT(isValid()); |
| + int32_t pluralIndex = StandardPlural::indexOrOtherIndexFromString(variant); |
| const SimplePatternFormatter *pattern = formatters[pluralIndex]; |
| if (pattern == NULL) { |
| - pattern = formatters[0]; |
| + pattern = formatters[StandardPlural::OTHER]; |
| } |
| return pattern; |
| } |
| |
| UnicodeString &QuantityFormatter::format( |
| - const Formattable& quantity, |
| + const Formattable &number, |
| const NumberFormat &fmt, |
| const PluralRules &rules, |
| UnicodeString &appendTo, |
| FieldPosition &pos, |
| UErrorCode &status) const { |
| + UnicodeString formattedNumber; |
| + StandardPlural::Form p = selectPlural(number, fmt, rules, formattedNumber, pos, status); |
| if (U_FAILURE(status)) { |
| return appendTo; |
| } |
| - UnicodeString count; |
| + const SimplePatternFormatter *pattern = formatters[p]; |
| + if (pattern == NULL) { |
| + pattern = formatters[StandardPlural::OTHER]; |
| + if (pattern == NULL) { |
| + status = U_INVALID_STATE_ERROR; |
| + return appendTo; |
| + } |
| + } |
| + return format(*pattern, formattedNumber, appendTo, pos, status); |
| +} |
| + |
| +// The following methods live here so that class PluralRules does not depend on number formatting, |
| +// and the SimplePatternFormatter does not depend on FieldPosition. |
| + |
| +StandardPlural::Form QuantityFormatter::selectPlural( |
| + const Formattable &number, |
| + const NumberFormat &fmt, |
| + const PluralRules &rules, |
| + UnicodeString &formattedNumber, |
| + FieldPosition &pos, |
| + UErrorCode &status) { |
| + if (U_FAILURE(status)) { |
| + return StandardPlural::OTHER; |
| + } |
| + UnicodeString pluralKeyword; |
| VisibleDigitsWithExponent digits; |
| const DecimalFormat *decFmt = dynamic_cast<const DecimalFormat *>(&fmt); |
| if (decFmt != NULL) { |
| - decFmt->initVisibleDigitsWithExponent(quantity, digits, status); |
| + decFmt->initVisibleDigitsWithExponent(number, digits, status); |
| if (U_FAILURE(status)) { |
| - return appendTo; |
| + return StandardPlural::OTHER; |
| } |
| - count = rules.select(digits); |
| + pluralKeyword = rules.select(digits); |
| + decFmt->format(digits, formattedNumber, pos, status); |
| } else { |
| - if (quantity.getType() == Formattable::kDouble) { |
| - count = rules.select(quantity.getDouble()); |
| - } else if (quantity.getType() == Formattable::kLong) { |
| - count = rules.select(quantity.getLong()); |
| - } else if (quantity.getType() == Formattable::kInt64) { |
| - count = rules.select((double) quantity.getInt64()); |
| + if (number.getType() == Formattable::kDouble) { |
| + pluralKeyword = rules.select(number.getDouble()); |
| + } else if (number.getType() == Formattable::kLong) { |
| + pluralKeyword = rules.select(number.getLong()); |
| + } else if (number.getType() == Formattable::kInt64) { |
| + pluralKeyword = rules.select((double) number.getInt64()); |
| } else { |
| status = U_ILLEGAL_ARGUMENT_ERROR; |
| - return appendTo; |
| + return StandardPlural::OTHER; |
| } |
| + fmt.format(number, formattedNumber, pos, status); |
| } |
| - CharString buffer; |
| - buffer.appendInvariantChars(count, status); |
| + return StandardPlural::orOtherFromString(pluralKeyword); |
| +} |
| + |
| +UnicodeString &QuantityFormatter::format( |
| + const SimplePatternFormatter &pattern, |
| + const UnicodeString &value, |
| + UnicodeString &appendTo, |
| + FieldPosition &pos, |
| + UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return appendTo; |
| } |
| - const SimplePatternFormatter *pattern = getByVariant(buffer.data()); |
| - if (pattern == NULL) { |
| - status = U_INVALID_STATE_ERROR; |
| - return appendTo; |
| - } |
| - UnicodeString formattedNumber; |
| - FieldPosition fpos(pos.getField()); |
| - if (decFmt != NULL) { |
| - decFmt->format(digits, formattedNumber, fpos, status); |
| - } else { |
| - fmt.format(quantity, formattedNumber, fpos, status); |
| - } |
| - const UnicodeString *params[1] = {&formattedNumber}; |
| - int32_t offsets[1]; |
| - pattern->formatAndAppend( |
| - params, |
| - UPRV_LENGTHOF(params), |
| - appendTo, |
| - offsets, |
| - UPRV_LENGTHOF(offsets), |
| - status); |
| - if (offsets[0] != -1) { |
| - if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { |
| - pos.setBeginIndex(fpos.getBeginIndex() + offsets[0]); |
| - pos.setEndIndex(fpos.getEndIndex() + offsets[0]); |
| + const UnicodeString *param = &value; |
| + int32_t offset; |
| + pattern.formatAndAppend(¶m, 1, appendTo, &offset, 1, status); |
| + if (pos.getBeginIndex() != 0 || pos.getEndIndex() != 0) { |
| + if (offset >= 0) { |
| + pos.setBeginIndex(pos.getBeginIndex() + offset); |
| + pos.setEndIndex(pos.getEndIndex() + offset); |
| + } else { |
| + pos.setBeginIndex(0); |
| + pos.setEndIndex(0); |
| } |
| } |
| return appendTo; |
| diff --git a/source/i18n/quantityformatter.h b/source/i18n/quantityformatter.h |
| index 4e19085..6052d33 100644 |
| --- a/source/i18n/quantityformatter.h |
| +++ b/source/i18n/quantityformatter.h |
| @@ -1,6 +1,6 @@ |
| /* |
| ****************************************************************************** |
| -* Copyright (C) 2014, International Business Machines |
| +* Copyright (C) 2014-2015, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ****************************************************************************** |
| * quantityformatter.h |
| @@ -14,6 +14,8 @@ |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| +#include "standardplural.h" |
| + |
| U_NAMESPACE_BEGIN |
| |
| class SimplePatternFormatter; |
| @@ -64,17 +66,14 @@ public: |
| void reset(); |
| |
| /** |
| - * Adds a plural variant. |
| - * |
| - * @param variant "zero", "one", "two", "few", "many", "other" |
| - * @param rawPattern the pattern for the variant e.g "{0} meters" |
| - * @param status any error returned here. |
| - * @return TRUE on success; FALSE if status was set to a non zero error. |
| - */ |
| - UBool add( |
| - const char *variant, |
| - const UnicodeString &rawPattern, |
| - UErrorCode &status); |
| + * Adds a plural variant if there is none yet for the plural form. |
| + * |
| + * @param variant "zero", "one", "two", "few", "many", "other" |
| + * @param rawPattern the pattern for the variant e.g "{0} meters" |
| + * @param status any error returned here. |
| + * @return TRUE on success; FALSE if status was set to a non zero error. |
| + */ |
| + UBool addIfAbsent(const char *variant, const UnicodeString &rawPattern, UErrorCode &status); |
| |
| /** |
| * returns TRUE if this object has at least the "other" variant. |
| @@ -89,27 +88,48 @@ public: |
| const SimplePatternFormatter *getByVariant(const char *variant) const; |
| |
| /** |
| - * Formats a quantity with this object appending the result to appendTo. |
| + * Formats a number with this object appending the result to appendTo. |
| * At least the "other" variant must be added to this object for this |
| * method to work. |
| * |
| - * @param quantity the single quantity. |
| - * @param fmt formats the quantity |
| + * @param number the single number. |
| + * @param fmt formats the number |
| * @param rules computes the plural variant to use. |
| * @param appendTo result appended here. |
| * @param status any error returned here. |
| * @return appendTo |
| */ |
| UnicodeString &format( |
| - const Formattable &quantity, |
| + const Formattable &number, |
| const NumberFormat &fmt, |
| const PluralRules &rules, |
| UnicodeString &appendTo, |
| FieldPosition &pos, |
| UErrorCode &status) const; |
| |
| + /** |
| + * Selects the standard plural form for the number/formatter/rules. |
| + */ |
| + static StandardPlural::Form selectPlural( |
| + const Formattable &number, |
| + const NumberFormat &fmt, |
| + const PluralRules &rules, |
| + UnicodeString &formattedNumber, |
| + FieldPosition &pos, |
| + UErrorCode &status); |
| + |
| + /** |
| + * Formats the pattern with the value and adjusts the FieldPosition. |
| + */ |
| + static UnicodeString &format( |
| + const SimplePatternFormatter &pattern, |
| + const UnicodeString &value, |
| + UnicodeString &appendTo, |
| + FieldPosition &pos, |
| + UErrorCode &status); |
| + |
| private: |
| - SimplePatternFormatter *formatters[6]; |
| + SimplePatternFormatter *formatters[StandardPlural::COUNT]; |
| }; |
| |
| U_NAMESPACE_END |
| diff --git a/source/i18n/reldatefmt.cpp b/source/i18n/reldatefmt.cpp |
| index b4bfe42..4d8ca06 100644 |
| --- a/source/i18n/reldatefmt.cpp |
| +++ b/source/i18n/reldatefmt.cpp |
| @@ -1,10 +1,10 @@ |
| /* |
| ****************************************************************************** |
| -* Copyright (C) 2014, International Business Machines Corporation and |
| -* others. All Rights Reserved. |
| +* Copyright (C) 2014-2015, International Business Machines Corporation and |
| +* others. All Rights Reserved. |
| ****************************************************************************** |
| -* |
| -* File RELDATEFMT.CPP |
| +* |
| +* File reldatefmt.cpp |
| ****************************************************************************** |
| */ |
| |
| @@ -186,7 +186,7 @@ static void initQuantityFormatter( |
| if (!getString(pluralBundle.getAlias(), rawPattern, status)) { |
| return; |
| } |
| - if (!formatter.add( |
| + if (!formatter.addIfAbsent( |
| ures_getKey(pluralBundle.getAlias()), |
| rawPattern, |
| status)) { |
| diff --git a/source/i18n/standardplural.cpp b/source/i18n/standardplural.cpp |
| new file mode 100644 |
| index 0000000..456e939 |
| --- /dev/null |
| +++ b/source/i18n/standardplural.cpp |
| @@ -0,0 +1,127 @@ |
| +/* |
| + ******************************************************************************* |
| + * Copyright (C) 2015, International Business Machines Corporation |
| + * and others. All Rights Reserved. |
| + ******************************************************************************* |
| + * standardplural.cpp |
| + * |
| + * created on: 2015dec14 |
| + * created by: Markus W. Scherer |
| + */ |
| + |
| +#include "unicode/utypes.h" |
| + |
| +#if !UCONFIG_NO_FORMATTING |
| + |
| +#include "unicode/unistr.h" |
| +#include "cstring.h" |
| +#include "standardplural.h" |
| +#include "uassert.h" |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +static const char *gKeywords[StandardPlural::COUNT] = { |
| + "zero", "one", "two", "few", "many", "other" |
| +}; |
| + |
| +const char *StandardPlural::getKeyword(Form p) { |
| + U_ASSERT(ZERO <= p && p < COUNT); |
| + return gKeywords[p]; |
| +} |
| + |
| +int32_t StandardPlural::indexOrNegativeFromString(const char *keyword) { |
| + switch (*keyword++) { |
| + case 'f': |
| + if (uprv_strcmp(keyword, "ew") == 0) { |
| + return FEW; |
| + } |
| + break; |
| + case 'm': |
| + if (uprv_strcmp(keyword, "any") == 0) { |
| + return MANY; |
| + } |
| + break; |
| + case 'o': |
| + if (uprv_strcmp(keyword, "ther") == 0) { |
| + return OTHER; |
| + } else if (uprv_strcmp(keyword, "ne") == 0) { |
| + return ONE; |
| + } |
| + break; |
| + case 't': |
| + if (uprv_strcmp(keyword, "wo") == 0) { |
| + return TWO; |
| + } |
| + break; |
| + case 'z': |
| + if (uprv_strcmp(keyword, "ero") == 0) { |
| + return ZERO; |
| + } |
| + break; |
| + default: |
| + break; |
| + } |
| + return -1; |
| +} |
| + |
| +static const UChar gZero[] = { 0x7A, 0x65, 0x72, 0x6F }; |
| +static const UChar gOne[] = { 0x6F, 0x6E, 0x65 }; |
| +static const UChar gTwo[] = { 0x74, 0x77, 0x6F }; |
| +static const UChar gFew[] = { 0x66, 0x65, 0x77 }; |
| +static const UChar gMany[] = { 0x6D, 0x61, 0x6E, 0x79 }; |
| +static const UChar gOther[] = { 0x6F, 0x74, 0x68, 0x65, 0x72 }; |
| + |
| +int32_t StandardPlural::indexOrNegativeFromString(const UnicodeString &keyword) { |
| + switch (keyword.length()) { |
| + case 3: |
| + if (keyword.compare(gOne, 3) == 0) { |
| + return ONE; |
| + } else if (keyword.compare(gTwo, 3) == 0) { |
| + return TWO; |
| + } else if (keyword.compare(gFew, 3) == 0) { |
| + return FEW; |
| + } |
| + break; |
| + case 4: |
| + if (keyword.compare(gMany, 4) == 0) { |
| + return MANY; |
| + } else if (keyword.compare(gZero, 4) == 0) { |
| + return ZERO; |
| + } |
| + break; |
| + case 5: |
| + if (keyword.compare(gOther, 5) == 0) { |
| + return OTHER; |
| + } |
| + break; |
| + default: |
| + break; |
| + } |
| + return -1; |
| +} |
| + |
| +int32_t StandardPlural::indexFromString(const char *keyword, UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return OTHER; } |
| + int32_t i = indexOrNegativeFromString(keyword); |
| + if (i >= 0) { |
| + return i; |
| + } else { |
| + errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| + return OTHER; |
| + } |
| +} |
| + |
| +int32_t StandardPlural::indexFromString(const UnicodeString &keyword, UErrorCode &errorCode) { |
| + if (U_FAILURE(errorCode)) { return OTHER; } |
| + int32_t i = indexOrNegativeFromString(keyword); |
| + if (i >= 0) { |
| + return i; |
| + } else { |
| + errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| + return OTHER; |
| + } |
| +} |
| + |
| +U_NAMESPACE_END |
| + |
| +#endif // !UCONFIG_NO_FORMATTING |
| diff --git a/source/i18n/standardplural.h b/source/i18n/standardplural.h |
| new file mode 100644 |
| index 0000000..8a8de21 |
| --- /dev/null |
| +++ b/source/i18n/standardplural.h |
| @@ -0,0 +1,130 @@ |
| +/* |
| + ******************************************************************************* |
| + * Copyright (C) 2015, International Business Machines Corporation |
| + * and others. All Rights Reserved. |
| + ******************************************************************************* |
| + * standardplural.h |
| + * |
| + * created on: 2015dec14 |
| + * created by: Markus W. Scherer |
| + */ |
| + |
| +#ifndef __STANDARDPLURAL_H__ |
| +#define __STANDARDPLURAL_H__ |
| + |
| +#include "unicode/utypes.h" |
| + |
| +#if !UCONFIG_NO_FORMATTING |
| + |
| +U_NAMESPACE_BEGIN |
| + |
| +class UnicodeString; |
| + |
| +/** |
| + * Standard CLDR plural form/category constants. |
| + * See http://www.unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules |
| + */ |
| +class U_I18N_API StandardPlural { |
| +public: |
| + enum Form { |
| + ZERO, |
| + ONE, |
| + TWO, |
| + FEW, |
| + MANY, |
| + OTHER, |
| + COUNT |
| + }; |
| + |
| + /** |
| + * @return the lowercase CLDR keyword string for the plural form |
| + */ |
| + static const char *getKeyword(Form p); |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the plural form corresponding to the keyword, or OTHER |
| + */ |
| + static Form orOtherFromString(const char *keyword) { |
| + return static_cast<Form>(indexOrOtherIndexFromString(keyword)); |
| + } |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the plural form corresponding to the keyword, or OTHER |
| + */ |
| + static Form orOtherFromString(const UnicodeString &keyword) { |
| + return static_cast<Form>(indexOrOtherIndexFromString(keyword)); |
| + } |
| + |
| + /** |
| + * Sets U_ILLEGAL_ARGUMENT_ERROR if the keyword is not a plural form. |
| + * |
| + * @param keyword for example "few" or "other" |
| + * @return the plural form corresponding to the keyword |
| + */ |
| + static Form fromString(const char *keyword, UErrorCode &errorCode) { |
| + return static_cast<Form>(indexFromString(keyword, errorCode)); |
| + } |
| + |
| + /** |
| + * Sets U_ILLEGAL_ARGUMENT_ERROR if the keyword is not a plural form. |
| + * |
| + * @param keyword for example "few" or "other" |
| + * @return the plural form corresponding to the keyword |
| + */ |
| + static Form fromString(const UnicodeString &keyword, UErrorCode &errorCode) { |
| + return static_cast<Form>(indexFromString(keyword, errorCode)); |
| + } |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword, or a negative value |
| + */ |
| + static int32_t indexOrNegativeFromString(const char *keyword); |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword, or a negative value |
| + */ |
| + static int32_t indexOrNegativeFromString(const UnicodeString &keyword); |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword, or OTHER |
| + */ |
| + static int32_t indexOrOtherIndexFromString(const char *keyword) { |
| + int32_t i = indexOrNegativeFromString(keyword); |
| + return i >= 0 ? i : OTHER; |
| + } |
| + |
| + /** |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword, or OTHER |
| + */ |
| + static int32_t indexOrOtherIndexFromString(const UnicodeString &keyword) { |
| + int32_t i = indexOrNegativeFromString(keyword); |
| + return i >= 0 ? i : OTHER; |
| + } |
| + |
| + /** |
| + * Sets U_ILLEGAL_ARGUMENT_ERROR if the keyword is not a plural form. |
| + * |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword |
| + */ |
| + static int32_t indexFromString(const char *keyword, UErrorCode &errorCode); |
| + |
| + /** |
| + * Sets U_ILLEGAL_ARGUMENT_ERROR if the keyword is not a plural form. |
| + * |
| + * @param keyword for example "few" or "other" |
| + * @return the index of the plural form corresponding to the keyword |
| + */ |
| + static int32_t indexFromString(const UnicodeString &keyword, UErrorCode &errorCode); |
| +}; |
| + |
| +U_NAMESPACE_END |
| + |
| +#endif // !UCONFIG_NO_FORMATTING |
| +#endif // __STANDARDPLURAL_H__ |
| diff --git a/source/i18n/unicode/measfmt.h b/source/i18n/unicode/measfmt.h |
| index 61a1e86..ee05427 100644 |
| --- a/source/i18n/unicode/measfmt.h |
| +++ b/source/i18n/unicode/measfmt.h |
| @@ -327,17 +327,19 @@ class U_I18N_API MeasureFormat : public Format { |
| // shared across instances. |
| ListFormatter *listFormatter; |
| |
| - const QuantityFormatter *getQuantityFormatter( |
| - int32_t index, |
| - int32_t widthIndex, |
| - UErrorCode &status) const; |
| + const SimplePatternFormatter *getFormatterOrNull( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const; |
| + |
| + const SimplePatternFormatter *getFormatter( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
| + UErrorCode &errorCode) const; |
| |
| - const SimplePatternFormatter *getPerUnitFormatter( |
| - int32_t index, |
| - int32_t widthIndex) const; |
| + const SimplePatternFormatter *getPluralFormatter( |
| + const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
| + UErrorCode &errorCode) const; |
| |
| const SimplePatternFormatter *getPerFormatter( |
| - int32_t widthIndex, |
| + UMeasureFormatWidth width, |
| UErrorCode &status) const; |
| |
| int32_t withPerUnitAndAppend( |
| diff --git a/source/i18n/unicode/measunit.h b/source/i18n/unicode/measunit.h |
| index 196832f..dc7c970 100644 |
| --- a/source/i18n/unicode/measunit.h |
| +++ b/source/i18n/unicode/measunit.h |
| @@ -185,6 +185,14 @@ class U_I18N_API MeasureUnit: public UObject { |
| |
| /** |
| * ICU use only. |
| + * @return the unit.getIndex() of the unit which has this unit.getType() and unit.getSubtype(), |
| + * or a negative value if there is no such unit |
| + * @internal |
| + */ |
| + static int32_t internalGetIndexForTypeAndSubtype(const char *type, const char *subtype); |
| + |
| + /** |
| + * ICU use only. |
| * @internal |
| */ |
| static MeasureUnit *resolveUnitPerUnit( |
| diff --git a/source/test/depstest/dependencies.txt b/source/test/depstest/dependencies.txt |
| index d77c828..89ad39c 100644 |
| --- a/source/test/depstest/dependencies.txt |
| +++ b/source/test/depstest/dependencies.txt |
| @@ -559,7 +559,7 @@ group: ures_cnv # ures_openU, path is a Unicode string |
| conversion resourcebundle |
| |
| group: resourcebundle |
| - resbund.o uresbund.o uresdata.o |
| + resource.o resbund.o uresbund.o uresdata.o |
| locavailable.o |
| # uloc_tag.c and uloc_keytype.cpp convert between |
| # old ICU/LDML/CLDR locale IDs and newer BCP 47 IDs. |
| @@ -893,7 +893,7 @@ group: format |
| resourcebundle parsepos uvector32 |
| |
| group: pluralrules |
| - plurrule.o upluralrules.o |
| + standardplural.o plurrule.o upluralrules.o |
| deps |
| digitlist # plurals depend on decimals |
| patternprops resourcebundle uvector uvector32 unifiedcache |
| diff --git a/source/test/intltest/itformat.cpp b/source/test/intltest/itformat.cpp |
| index f9f815d..deae7ba 100644 |
| --- a/source/test/intltest/itformat.cpp |
| +++ b/source/test/intltest/itformat.cpp |
| @@ -53,7 +53,6 @@ |
| #include "plurfmts.h" // PluralFormatTest |
| #include "selfmts.h" // PluralFormatTest |
| #include "dtifmtts.h" // DateIntervalFormatTest |
| -#include "tufmtts.h" // TimeUnitTest |
| #include "locnmtst.h" // LocaleDisplayNamesTest |
| #include "dcfmtest.h" // DecimalFormatTest |
| #include "listformattertest.h" // ListFormatterTest |
| @@ -64,6 +63,7 @@ extern IntlTest *createGenderInfoTest(); |
| #if !UCONFIG_NO_BREAK_ITERATION |
| extern IntlTest *createRelativeDateTimeFormatterTest(); |
| #endif |
| +extern IntlTest *createTimeUnitTest(); |
| extern IntlTest *createMeasureFormatTest(); |
| extern IntlTest *createNumberFormatSpecificationTest(); |
| extern IntlTest *createScientificNumberFormatterTest(); |
| @@ -139,7 +139,15 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam |
| TESTCLASS(35,PluralRulesTest); |
| TESTCLASS(36,PluralFormatTest); |
| TESTCLASS(37,DateIntervalFormatTest); |
| - TESTCLASS(38,TimeUnitTest); |
| + case 38: |
| + name = "TimeUnitTest"; |
| + if (exec) { |
| + logln("TimeUnitTest test---"); |
| + logln((UnicodeString)""); |
| + LocalPointer<IntlTest> test(createTimeUnitTest()); |
| + callTest(*test, par); |
| + } |
| + break; |
| TESTCLASS(39,SelectFormatTest); |
| TESTCLASS(40,LocaleDisplayNamesTest); |
| #if !UCONFIG_NO_REGULAR_EXPRESSIONS |
| diff --git a/source/test/intltest/itutil.cpp b/source/test/intltest/itutil.cpp |
| index 49655fa..4c4f604 100644 |
| --- a/source/test/intltest/itutil.cpp |
| +++ b/source/test/intltest/itutil.cpp |
| @@ -124,6 +124,7 @@ void IntlTestUtilities::runIndexedTest( int32_t index, UBool exec, const char* & |
| LocalPointer<IntlTest> test(createQuantityFormatterTest()); |
| callTest(*test, par); |
| } |
| + break; |
| case 23: |
| name = "PluralMapTest"; |
| if (exec) { |
| diff --git a/source/test/intltest/measfmttest.cpp b/source/test/intltest/measfmttest.cpp |
| index f2f2d09..4732f61 100644 |
| --- a/source/test/intltest/measfmttest.cpp |
| +++ b/source/test/intltest/measfmttest.cpp |
| @@ -61,6 +61,7 @@ private: |
| void TestGroupingSeparator(); |
| void TestDoubleZero(); |
| void TestUnitPerUnitResolution(); |
| + void TestIndividualPluralFallback(); |
| void verifyFormat( |
| const char *description, |
| const MeasureFormat &fmt, |
| @@ -141,6 +142,7 @@ void MeasureFormatTest::runIndexedTest( |
| TESTCASE_AUTO(TestGroupingSeparator); |
| TESTCASE_AUTO(TestDoubleZero); |
| TESTCASE_AUTO(TestUnitPerUnitResolution); |
| + TESTCASE_AUTO(TestIndividualPluralFallback); |
| TESTCASE_AUTO_END; |
| } |
| |
| @@ -1588,6 +1590,19 @@ void MeasureFormatTest::TestUnitPerUnitResolution() { |
| assertEquals("", "50 psi", actual); |
| } |
| |
| +void MeasureFormatTest::TestIndividualPluralFallback() { |
| + // See ticket #11986 "incomplete fallback in MeasureFormat". |
| + // In CLDR 28, fr_CA temperature-generic/short has only the "one" form, |
| + // and falls back to fr for the "other" form. |
| + IcuTestErrorCode errorCode(*this, "TestIndividualPluralFallback"); |
| + MeasureFormat mf("fr_CA", UMEASFMT_WIDTH_SHORT, errorCode); |
| + LocalPointer<Measure> twoDeg( |
| + new Measure(2, MeasureUnit::createGenericTemperature(errorCode), errorCode), errorCode); |
| + UnicodeString expected = UNICODE_STRING_SIMPLE("2\\u00B0").unescape(); |
| + UnicodeString actual; |
| + assertEquals("2 deg temp in fr_CA", expected, mf.format(twoDeg.orphan(), actual, errorCode)); |
| +} |
| + |
| void MeasureFormatTest::verifyFieldPosition( |
| const char *description, |
| const MeasureFormat &fmt, |
| diff --git a/source/test/intltest/quantityformattertest.cpp b/source/test/intltest/quantityformattertest.cpp |
| index 0167075..c641de2 100644 |
| --- a/source/test/intltest/quantityformattertest.cpp |
| +++ b/source/test/intltest/quantityformattertest.cpp |
| @@ -1,7 +1,7 @@ |
| /* |
| ******************************************************************************* |
| -* Copyright (C) 2014, International Business Machines Corporation and * |
| -* others. All Rights Reserved. * |
| +* Copyright (C) 2014-2015, International Business Machines Corporation and |
| +* others. All Rights Reserved. |
| ******************************************************************************* |
| * |
| * File QUANTITYFORMATTERTEST.CPP |
| @@ -37,25 +37,25 @@ void QuantityFormatterTest::TestBasic() { |
| QuantityFormatter fmt; |
| assertFalse( |
| "adding bad variant", |
| - fmt.add("a bad variant", "{0} pounds", status)); |
| + fmt.addIfAbsent("a bad variant", "{0} pounds", status)); |
| assertEquals("adding bad variant status", U_ILLEGAL_ARGUMENT_ERROR, status); |
| status = U_ZERO_ERROR; |
| assertFalse( |
| "Adding bad pattern", |
| - fmt.add("other", "{0} {1} too many placeholders", status)); |
| + fmt.addIfAbsent("other", "{0} {1} too many placeholders", status)); |
| assertEquals("adding bad pattern status", U_ILLEGAL_ARGUMENT_ERROR, status); |
| status = U_ZERO_ERROR; |
| assertFalse("isValid with no patterns", fmt.isValid()); |
| assertTrue( |
| "Adding good pattern with no placeholders", |
| - fmt.add("other", "no placeholder", status)); |
| + fmt.addIfAbsent("zero", "no placeholder", status)); |
| assertTrue( |
| "Adding good pattern", |
| - fmt.add("other", "{0} pounds", status)); |
| + fmt.addIfAbsent("other", "{0} pounds", status)); |
| assertTrue("isValid with other", fmt.isValid()); |
| assertTrue( |
| "Adding good pattern", |
| - fmt.add("one", "{0} pound", status)); |
| + fmt.addIfAbsent("one", "{0} pound", status)); |
| |
| assertEquals( |
| "getByVariant", |
| diff --git a/source/test/intltest/reldatefmttest.cpp b/source/test/intltest/reldatefmttest.cpp |
| index 91f5483..f11adb6 100644 |
| --- a/source/test/intltest/reldatefmttest.cpp |
| +++ b/source/test/intltest/reldatefmttest.cpp |
| @@ -231,9 +231,9 @@ static WithQuantityExpected kSerbian[] = { |
| }; |
| |
| static WithQuantityExpected kSerbianNarrow[] = { |
| - {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 0 \\u043c\\u0435\\u0441."}, |
| - {1.2, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 1,2 \\u043c\\u0435\\u0441."}, |
| - {21.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 21 \\u043c\\u0435\\u0441."} |
| + {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 0 \\u043c."}, |
| + {1.2, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 1,2 \\u043c."}, |
| + {21.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 21 \\u043c."} |
| }; |
| |
| static WithoutQuantityExpected kEnglishNoQuantity[] = { |
| diff --git a/source/test/intltest/tufmtts.cpp b/source/test/intltest/tufmtts.cpp |
| index 8ed9b9f..6b1ed5a 100644 |
| --- a/source/test/intltest/tufmtts.cpp |
| +++ b/source/test/intltest/tufmtts.cpp |
| @@ -1,5 +1,5 @@ |
| /******************************************************************** |
| - * Copyright (c) 2008-2014, International Business Machines Corporation and |
| + * Copyright (c) 2008-2015, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ********************************************************************/ |
| |
| @@ -11,9 +11,9 @@ |
| #include "unicode/tmunit.h" |
| #include "unicode/tmutamt.h" |
| #include "unicode/tmutfmt.h" |
| -#include "tufmtts.h" |
| -#include "cmemory.h" |
| #include "unicode/ustring.h" |
| +#include "cmemory.h" |
| +#include "intltest.h" |
| |
| //TODO: put as compilation flag |
| //#define TUFMTTS_DEBUG 1 |
| @@ -22,16 +22,60 @@ |
| #include <iostream> |
| #endif |
| |
| -void TimeUnitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { |
| - if (exec) logln("TestSuite TimeUnitTest"); |
| - switch (index) { |
| - TESTCASE(0, testBasic); |
| - TESTCASE(1, testAPI); |
| - TESTCASE(2, testGreekWithFallback); |
| - TESTCASE(3, testGreekWithSanitization); |
| - TESTCASE(4, test10219Plurals); |
| - default: name = ""; break; |
| +class TimeUnitTest : public IntlTest { |
| + void runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/ ) { |
| + if (exec) logln("TestSuite TimeUnitTest"); |
| + TESTCASE_AUTO_BEGIN; |
| + TESTCASE_AUTO(testBasic); |
| + TESTCASE_AUTO(testAPI); |
| + TESTCASE_AUTO(testGreekWithFallback); |
| + TESTCASE_AUTO(testGreekWithSanitization); |
| + TESTCASE_AUTO(test10219Plurals); |
| + TESTCASE_AUTO(TestBritishShortHourFallback); |
| + TESTCASE_AUTO_END; |
| } |
| + |
| +public: |
| + /** |
| + * Performs basic tests |
| + **/ |
| + void testBasic(); |
| + |
| + /** |
| + * Performs API tests |
| + **/ |
| + void testAPI(); |
| + |
| + /** |
| + * Performs tests for Greek |
| + * This tests that requests for short unit names correctly fall back |
| + * to long unit names for a locale where the locale data does not |
| + * provide short unit names. As of CLDR 1.9, Greek is one such language. |
| + **/ |
| + void testGreekWithFallback(); |
| + |
| + /** |
| + * Performs tests for Greek |
| + * This tests that if the plural count listed in time unit format does not |
| + * match those in the plural rules for the locale, those plural count in |
| + * time unit format will be ingored and subsequently, fall back will kick in |
| + * which is tested above. |
| + * Without data sanitization, setNumberFormat() would crash. |
| + * As of CLDR shiped in ICU4.8, Greek is one such language. |
| + */ |
| + void testGreekWithSanitization(); |
| + |
| + /** |
| + * Performs unit test for ticket 10219 making sure that plurals work |
| + * correctly with rounding. |
| + */ |
| + void test10219Plurals(); |
| + |
| + void TestBritishShortHourFallback(); |
| +}; |
| + |
| +extern IntlTest *createTimeUnitTest() { |
| + return new TimeUnitTest(); |
| } |
| |
| // This function is more lenient than equals operator as it considers integer 3 hours and |
| @@ -469,4 +513,16 @@ void TimeUnitTest::test10219Plurals() { |
| } |
| } |
| |
| +void TimeUnitTest::TestBritishShortHourFallback() { |
| + // See ticket #11986 "incomplete fallback in MeasureFormat". |
| + UErrorCode status = U_ZERO_ERROR; |
| + Formattable oneHour(new TimeUnitAmount(1, TimeUnit::UTIMEUNIT_HOUR, status)); |
| + Locale en_GB("en_GB"); |
| + TimeUnitFormat formatter(en_GB, UTMUTFMT_ABBREVIATED_STYLE, status); |
| + UnicodeString result; |
| + formatter.format(oneHour, result, status); |
| + assertSuccess("TestBritishShortHourFallback()", status); |
| + assertEquals("TestBritishShortHourFallback()", UNICODE_STRING_SIMPLE("1 hr"), result); |
| +} |
| + |
| #endif |
| diff --git a/source/test/intltest/tufmtts.h b/source/test/intltest/tufmtts.h |
| index cd4a485..e69de29 100644 |
| --- a/source/test/intltest/tufmtts.h |
| +++ b/source/test/intltest/tufmtts.h |
| @@ -1,63 +0,0 @@ |
| -/******************************************************************** |
| - * COPYRIGHT: |
| - * Copyright (c) 2008-2013, International Business Machines Corporation |
| - * and others. All Rights Reserved. |
| - ********************************************************************/ |
| - |
| -#ifndef __INTLTESTTIMEUNITTEST__ |
| -#define __INTLTESTTIMEUNITTEST__ |
| - |
| - |
| -#if !UCONFIG_NO_FORMATTING |
| - |
| -#include "unicode/utypes.h" |
| -#include "unicode/locid.h" |
| -#include "intltest.h" |
| - |
| -/** |
| - * Test basic functionality of various API functions |
| - **/ |
| -class TimeUnitTest: public IntlTest { |
| - void runIndexedTest( int32_t index, UBool exec, const char* &name, char* par = NULL ); |
| - |
| -public: |
| - /** |
| - * Performs basic tests |
| - **/ |
| - void testBasic(); |
| - |
| - /** |
| - * Performs API tests |
| - **/ |
| - void testAPI(); |
| - |
| - /** |
| - * Performs tests for Greek |
| - * This tests that requests for short unit names correctly fall back |
| - * to long unit names for a locale where the locale data does not |
| - * provide short unit names. As of CLDR 1.9, Greek is one such language. |
| - **/ |
| - void testGreekWithFallback(); |
| - |
| - /** |
| - * Performs tests for Greek |
| - * This tests that if the plural count listed in time unit format does not |
| - * match those in the plural rules for the locale, those plural count in |
| - * time unit format will be ingored and subsequently, fall back will kick in |
| - * which is tested above. |
| - * Without data sanitization, setNumberFormat() would crash. |
| - * As of CLDR shiped in ICU4.8, Greek is one such language. |
| - */ |
| - void testGreekWithSanitization(); |
| - |
| - /** |
| - * Performs unit test for ticket 10219 making sure that plurals work |
| - * correctly with rounding. |
| - */ |
| - void test10219Plurals(); |
| - |
| -}; |
| - |
| -#endif /* #if !UCONFIG_NO_FORMATTING */ |
| - |
| -#endif |