/******************************************************************** * COPYRIGHT: * Copyright (c) 1997-2010, International Business Machines Corporation and * others. All Rights Reserved. * Copyright (C) 2010 , Yahoo! Inc. ******************************************************************** * * File SELFMT.CPP * * Modification History: * * Date Name Description * 11/11/09 kirtig Finished first cut of implementation. * 11/16/09 kirtig Improved version ********************************************************************/ #include // for 'typeid' to work #include "unicode/utypes.h" #include "unicode/ustring.h" #include "unicode/ucnv_err.h" #include "unicode/uchar.h" #include "unicode/umsg.h" #include "unicode/rbnf.h" #include "cmemory.h" #include "util.h" #include "uassert.h" #include "ustrfmt.h" #include "uvector.h" #include "unicode/selfmt.h" #include "selfmtimpl.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SelectFormat) #define MAX_KEYWORD_SIZE 30 static const UChar SELECT_KEYWORD_OTHER[] = {LOW_O, LOW_T, LOW_H, LOW_E, LOW_R, 0}; SelectFormat::SelectFormat(const UnicodeString& pat, UErrorCode& status) : parsedValuesHash(NULL) { if (U_FAILURE(status)) { return; } initHashTable(status); applyPattern(pat, status); } SelectFormat::SelectFormat(const SelectFormat& other) : Format(other), parsedValuesHash(NULL) { UErrorCode status = U_ZERO_ERROR; pattern = other.pattern; copyHashtable(other.parsedValuesHash, status); } SelectFormat::~SelectFormat() { cleanHashTable(); } void SelectFormat::initHashTable(UErrorCode &status) { if (U_FAILURE(status)) { return; } // has inited if (parsedValuesHash != NULL) { return; } parsedValuesHash = new Hashtable(TRUE, status); if (U_FAILURE(status)) { cleanHashTable(); return; } else { if (parsedValuesHash == NULL) { status = U_MEMORY_ALLOCATION_ERROR; return; } } // to use hashtable->equals(), must set Value Compartor. parsedValuesHash->setValueComparator(uhash_compareCaselessUnicodeString); } void SelectFormat::cleanHashTable() { if (parsedValuesHash != NULL) { delete parsedValuesHash; parsedValuesHash = NULL; } } void SelectFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) { if (U_FAILURE(status)) { return; } pattern = newPattern; enum State{ startState, keywordState, pastKeywordState, phraseState}; //Initialization UnicodeString keyword ; UnicodeString phrase ; UnicodeString* ptrPhrase ; int32_t braceCount = 0; if (parsedValuesHash == NULL) { initHashTable(status); if (U_FAILURE(status)) { return; } } parsedValuesHash->removeAll(); parsedValuesHash->setValueDeleter(uhash_deleteUnicodeString); //Process the state machine State state = startState; for (int32_t i = 0; i < pattern.length(); ++i) { //Get the character and check its type UChar ch = pattern.charAt(i); CharacterClass type = classifyCharacter(ch); //Allow any character in phrase but nowhere else if ( type == tOther ) { if ( state == phraseState ){ phrase += ch; continue; }else { status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; } } //Process the state machine switch (state) { //At the start of pattern case startState: switch (type) { case tSpace: break; case tStartKeyword: state = keywordState; keyword += ch; break; //If anything else is encountered, it's a syntax error default: status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; }//end of switch(type) break; //Handle the keyword state case keywordState: switch (type) { case tSpace: state = pastKeywordState; break; case tStartKeyword: case tContinueKeyword: keyword += ch; break; case tLeftBrace: state = phraseState; break; //If anything else is encountered, it's a syntax error default: status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; }//end of switch(type) break; //Handle the pastkeyword state case pastKeywordState: switch (type) { case tSpace: break; case tLeftBrace: state = phraseState; break; //If anything else is encountered, it's a syntax error default: status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; }//end of switch(type) break; //Handle the phrase state case phraseState: switch (type) { case tLeftBrace: braceCount++; phrase += ch; break; case tRightBrace: //Matching keyword, phrase pair found if (braceCount == 0){ //Check validity of keyword if (parsedValuesHash->get(keyword) != NULL) { status = U_DUPLICATE_KEYWORD; cleanHashTable(); return; } if (keyword.length() == 0) { status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; } //Store the keyword, phrase pair in hashTable ptrPhrase = new UnicodeString(phrase); parsedValuesHash->put( keyword, ptrPhrase, status); //Reinitialize keyword.remove(); phrase.remove(); ptrPhrase = NULL; state = startState; } if (braceCount > 0){ braceCount-- ; phrase += ch; } break; default: phrase += ch; }//end of switch(type) break; //Handle the default case of switch(state) default: status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; }//end of switch(state) } //Check if the state machine is back to startState if ( state != startState){ status = U_PATTERN_SYNTAX_ERROR; cleanHashTable(); return; } //Check if "other" keyword is present if ( !checkSufficientDefinition() ) { status = U_DEFAULT_KEYWORD_MISSING; cleanHashTable(); } return; } UnicodeString& SelectFormat::format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { switch (obj.getType()) { case Formattable::kString: return format(obj.getString(), appendTo, pos, status); default: if( U_SUCCESS(status) ){ status = U_ILLEGAL_ARGUMENT_ERROR; } return appendTo; } } UnicodeString& SelectFormat::format(const UnicodeString& keyword, UnicodeString& appendTo, FieldPosition& /*pos */, UErrorCode& status) const { if (U_FAILURE(status)) return appendTo; if (parsedValuesHash == NULL) { status = U_INVALID_FORMAT_ERROR; return appendTo; } //Check for the validity of the keyword if ( !checkValidKeyword(keyword) ){ status = U_ILLEGAL_ARGUMENT_ERROR; return appendTo; } UnicodeString *selectedPattern = (UnicodeString *)parsedValuesHash->get(keyword); if (selectedPattern == NULL) { selectedPattern = (UnicodeString *)parsedValuesHash->get(SELECT_KEYWORD_OTHER); } return appendTo += *selectedPattern; } UnicodeString& SelectFormat::toPattern(UnicodeString& appendTo) { return appendTo += pattern; } SelectFormat::CharacterClass SelectFormat::classifyCharacter(UChar ch) const{ if ((ch >= CAP_A) && (ch <= CAP_Z)) { return tStartKeyword; } if ((ch >= LOW_A) && (ch <= LOW_Z)) { return tStartKeyword; } if ((ch >= U_ZERO) && (ch <= U_NINE)) { return tContinueKeyword; } if ( uprv_isRuleWhiteSpace(ch) ){ return tSpace; } switch (ch) { case LEFTBRACE: return tLeftBrace; case RIGHTBRACE: return tRightBrace; case HYPHEN: case LOWLINE: return tContinueKeyword; default : return tOther; } } UBool SelectFormat::checkSufficientDefinition() { // Check that at least the default rule is defined. return (parsedValuesHash != NULL && parsedValuesHash->get(SELECT_KEYWORD_OTHER) != NULL) ; } UBool SelectFormat::checkValidKeyword(const UnicodeString& argKeyword ) const{ int32_t len = argKeyword.length(); if (len < 1){ return FALSE; } CharacterClass type = classifyCharacter(argKeyword.charAt(0)); if( type != tStartKeyword ){ return FALSE; } for (int32_t i = 0; i < argKeyword.length(); ++i) { type = classifyCharacter(argKeyword.charAt(i)); if( type != tStartKeyword && type != tContinueKeyword ){ return FALSE; } } return TRUE; } Format* SelectFormat::clone() const { return new SelectFormat(*this); } SelectFormat& SelectFormat::operator=(const SelectFormat& other) { if (this != &other) { UErrorCode status = U_ZERO_ERROR; pattern = other.pattern; copyHashtable(other.parsedValuesHash, status); } return *this; } UBool SelectFormat::operator==(const Format& other) const { if( this == &other){ return TRUE; } if (typeid(*this) != typeid(other)) { return FALSE; } SelectFormat* fmt = (SelectFormat*)&other; Hashtable* hashOther = fmt->parsedValuesHash; if ( parsedValuesHash == NULL && hashOther == NULL) return TRUE; if ( parsedValuesHash == NULL || hashOther == NULL) return FALSE; return parsedValuesHash->equals(*hashOther); } UBool SelectFormat::operator!=(const Format& other) const { return !operator==(other); } void SelectFormat::parseObject(const UnicodeString& /*source*/, Formattable& /*result*/, ParsePosition& pos) const { // TODO: not yet supported in icu4j and icu4c pos.setErrorIndex(pos.getIndex()); } void SelectFormat::copyHashtable(Hashtable *other, UErrorCode& status) { if (U_FAILURE(status)) { return; } if (other == NULL) { cleanHashTable(); return; } if (parsedValuesHash == NULL) { initHashTable(status); if (U_FAILURE(status)) { return; } } parsedValuesHash->removeAll(); parsedValuesHash->setValueDeleter(uhash_deleteUnicodeString); int32_t pos = -1; const UHashElement* elem = NULL; // walk through the hash table and create a deep clone while ((elem = other->nextElement(pos)) != NULL){ const UHashTok otherKeyTok = elem->key; UnicodeString* otherKey = (UnicodeString*)otherKeyTok.pointer; const UHashTok otherKeyToVal = elem->value; UnicodeString* otherValue = (UnicodeString*)otherKeyToVal.pointer; parsedValuesHash->put(*otherKey, new UnicodeString(*otherValue), status); if (U_FAILURE(status)){ cleanHashTable(); return; } } } U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ //eof