#include <string> #if !defined (STLPORT) || !defined (_STLP_USE_NO_IOSTREAMS) # include <fstream> # include <locale> # include <stdexcept> # include <cstdio> // for WEOF # include "cppunit/cppunit_proxy.h" # if !defined (STLPORT) || defined(_STLP_USE_NAMESPACES) using namespace std; # endif // // TestCase class // class CodecvtTest : public CPPUNIT_NS::TestCase { CPPUNIT_TEST_SUITE(CodecvtTest); #if defined (STLPORT) && defined (_STLP_NO_MEMBER_TEMPLATES) CPPUNIT_IGNORE; #endif CPPUNIT_TEST(variable_encoding); CPPUNIT_STOP_IGNORE; #if defined (STLPORT) && (defined (_STLP_NO_WCHAR_T) || !defined (_STLP_USE_EXCEPTIONS)) CPPUNIT_IGNORE; #endif CPPUNIT_TEST(in_out_test); CPPUNIT_TEST(length_test); CPPUNIT_TEST(imbue_while_reading); CPPUNIT_TEST(special_encodings); CPPUNIT_TEST_SUITE_END(); protected: void variable_encoding(); void in_out_test(); void length_test(); void imbue_while_reading(); void special_encodings(); }; CPPUNIT_TEST_SUITE_REGISTRATION(CodecvtTest); #if defined (STLPORT) # define __NO_THROW _STLP_NOTHROW #else # define __NO_THROW throw() #endif /* Codecvt facet eating some characters from the external buffer. * Transform '01' in 'a' */ struct eater_codecvt : public codecvt<char, char, mbstate_t> { typedef codecvt<char,char,mbstate_t> base; explicit eater_codecvt(size_t refs = 0) : base(refs) {} // primitive conversion virtual base::result do_in(mbstate_t& mb, const char* ebegin, const char* eend, const char*& ecur, char* ibegin, char* iend, char*& icur) const __NO_THROW { char *state = (char*)&mb; ecur = ebegin; icur = ibegin; while (ecur != eend) { if (icur == iend) return partial; if (*ecur == '0' || *state == 1) { if (*state != 1) { ++ecur; } if (ecur == eend) { *state = 1; return ok; } if (*ecur == '1') { *icur = 'a'; } else { *(icur++) = '0'; if (icur == iend) { if (*state != 1) { --ecur; } return partial; } *icur = *ecur; } } else { *icur = *ecur; } *state = 0; ++icur; ++ecur; } return ok; } // claim it's not a null-conversion virtual bool do_always_noconv() const __NO_THROW { return false; } // claim it doesn't have a fixed-length encoding virtual int do_encoding() const __NO_THROW { return 0; } // implemented for consistency with do_in overload virtual int do_length(mbstate_t &state, const char *efrom, const char *eend, size_t m) const { char *ibegin = new char[m]; const char *ecur = efrom; char *icur = ibegin; mbstate_t tmp = state; do_in(tmp, efrom, eend, ecur, ibegin, ibegin + m, icur); delete[] ibegin; return ecur - efrom; } virtual int do_max_length() const __NO_THROW { return 2; } #ifdef __DMC__ static locale::id id; #endif }; #ifdef __DMC__ locale::id eater_codecvt::id; locale::id& _GetFacetId(const eater_codecvt*) { return eater_codecvt::id; } #endif /* Codecvt facet generating more characters than the ones read from the * external buffer, transform '01' in 'abc' * This kind of facet do not allow systematical positionning in the external * buffer (tellg -> -1), when you just read a 'a' you are at an undefined * external buffer position. */ struct generator_codecvt : public codecvt<char, char, mbstate_t> { typedef codecvt<char,char,mbstate_t> base; explicit generator_codecvt(size_t refs = 0) : base(refs) {} // primitive conversion virtual base::result do_in(mbstate_t& mb, const char* ebegin, const char* eend, const char*& ecur, char* ibegin, char* iend, char*& icur) const __NO_THROW { //Access the mbstate information in a portable way: char *state = (char*)&mb; ecur = ebegin; icur = ibegin; if (icur == iend) return ok; if (*state == 2) { *(icur++) = 'b'; if (icur == iend) { *state = 3; return ok; } *(icur++) = 'c'; *state = 0; } else if (*state == 3) { *(icur++) = 'c'; *state = 0; } while (ecur != eend) { if (icur == iend) return ok; if (*ecur == '0' || *state == 1) { if (*state != 1) { ++ecur; } if (ecur == eend) { *state = 1; return partial; } if (*ecur == '1') { *(icur++) = 'a'; if (icur == iend) { *state = 2; return ok; } *(icur++) = 'b'; if (icur == iend) { *state = 3; return ok; } *icur = 'c'; } else { *(icur++) = '0'; if (icur == iend) { if (*state != 1) { --ecur; } return ok; } *icur = *ecur; } } else { *icur = *ecur; } *state = 0; ++icur; ++ecur; } return ok; } // claim it's not a null-conversion virtual bool do_always_noconv() const __NO_THROW { return false; } // claim it doesn't have a fixed-length encoding virtual int do_encoding() const __NO_THROW { return 0; } // implemented for consistency with do_in overload virtual int do_length(mbstate_t &mb, const char *efrom, const char *eend, size_t m) const { const char *state = (const char*)&mb; int offset = 0; if (*state == 2) offset = 2; else if (*state == 3) offset = 1; char *ibegin = new char[m + offset]; const char *ecur = efrom; char *icur = ibegin; mbstate_t tmpState = mb; do_in(tmpState, efrom, eend, ecur, ibegin, ibegin + m + offset, icur); /* char *state = (char*)&tmpState; if (*state != 0) { if (*state == 1) --ecur; else if (*state == 2 || *state == 3) { //Undefined position, we return -1: ecur = efrom - 1; } } else { if (*((char*)&mb) != 0) { //We take into account the character that hasn't been counted yet in //the previous decoding step: ecur++; } } */ delete[] ibegin; return (int)min((size_t)(ecur - efrom), m); } virtual int do_max_length() const __NO_THROW { return 0; } #ifdef __DMC__ static locale::id id; #endif }; #ifdef __DMC__ locale::id generator_codecvt::id; locale::id& _GetFacetId(const generator_codecvt*) { return generator_codecvt::id; } #endif // // tests implementation // void CodecvtTest::variable_encoding() { #if !defined (STLPORT) || !defined (_STLP_NO_MEMBER_TEMPLATES) //We first generate the file used for test: const char* fileName = "test_file.txt"; { ofstream ostr(fileName); //Maybe we simply do not have write access to repository CPPUNIT_ASSERT( ostr.good() ); for (int i = 0; i < 2048; ++i) { ostr << "0123456789"; } CPPUNIT_ASSERT( ostr.good() ); } { ifstream istr(fileName); CPPUNIT_ASSERT( istr.good() ); CPPUNIT_ASSERT( !istr.eof() ); eater_codecvt codec(1); locale loc(locale::classic(), &codec); istr.imbue(loc); CPPUNIT_ASSERT( istr.good() ); CPPUNIT_ASSERT( (int)istr.tellg() == 0 ); int theoricalPos = 0; do { int c = istr.get(); if (char_traits<char>::eq_int_type(c, char_traits<char>::eof())) { break; } ++theoricalPos; if (c == 'a') { ++theoricalPos; } CPPUNIT_ASSERT( (int)istr.tellg() == theoricalPos ); } while (!istr.eof()); CPPUNIT_ASSERT( istr.eof() ); } # if 0 /* This test is broken, not sure if it is really possible to get a position in * a locale having a codecvt such as generator_codecvt. Maybe generator_codecvt * is not a valid theorical example of codecvt implementation. */ { ifstream istr(fileName); CPPUNIT_ASSERT( istr.good() ); CPPUNIT_ASSERT( !istr.eof() ); generator_codecvt codec(1); locale loc(locale::classic(), &codec); istr.imbue(loc); CPPUNIT_ASSERT( istr.good() ); CPPUNIT_ASSERT( (int)istr.tellg() == 0 ); int theoricalPos = 0; int theoricalTellg; do { char c = istr.get(); if (c == char_traits<char>::eof()) { break; } switch (c) { case 'a': case 'b': theoricalTellg = -1; break; case 'c': ++theoricalPos; default: ++theoricalPos; theoricalTellg = theoricalPos; break; } if ((int)istr.tellg() != theoricalTellg) { CPPUNIT_ASSERT( (int)istr.tellg() == theoricalTellg ); } } while (!istr.eof()); CPPUNIT_ASSERT( istr.eof() ); } # endif #endif } void CodecvtTest::in_out_test() { #if !defined (STLPORT) || !(defined (_STLP_NO_WCHAR_T) || !defined (_STLP_USE_EXCEPTIONS)) try { locale loc(""); typedef codecvt<wchar_t, char, mbstate_t> cdecvt_type; if (has_facet<cdecvt_type>(loc)) { cdecvt_type const& cdect = use_facet<cdecvt_type>(loc); { cdecvt_type::state_type state; memset(&state, 0, sizeof(cdecvt_type::state_type)); string from("abcdef"); const char* next_from; wchar_t to[1]; wchar_t *next_to; cdecvt_type::result res = cdect.in(state, from.data(), from.data() + from.size(), next_from, to, to + sizeof(to) / sizeof(wchar_t), next_to); CPPUNIT_ASSERT( res == cdecvt_type::ok ); CPPUNIT_ASSERT( next_from == from.data() + 1 ); CPPUNIT_ASSERT( next_to == &to[0] + 1 ); CPPUNIT_ASSERT( to[0] == L'a'); } { cdecvt_type::state_type state; memset(&state, 0, sizeof(cdecvt_type::state_type)); wstring from(L"abcdef"); const wchar_t* next_from; char to[1]; char *next_to; cdecvt_type::result res = cdect.out(state, from.data(), from.data() + from.size(), next_from, to, to + sizeof(to) / sizeof(char), next_to); CPPUNIT_ASSERT( res == cdecvt_type::ok ); CPPUNIT_ASSERT( next_from == from.data() + 1 ); CPPUNIT_ASSERT( next_to == &to[0] + 1 ); CPPUNIT_ASSERT( to[0] == 'a'); } } } catch (runtime_error const&) { } catch (...) { CPPUNIT_FAIL; } #endif } void CodecvtTest::length_test() { #if !defined (STLPORT) || !(defined (_STLP_NO_WCHAR_T) || !defined (_STLP_USE_EXCEPTIONS)) try { locale loc(""); typedef codecvt<wchar_t, char, mbstate_t> cdecvt_type; if (has_facet<cdecvt_type>(loc)) { cdecvt_type const& cdect = use_facet<cdecvt_type>(loc); { cdecvt_type::state_type state; memset(&state, 0, sizeof(cdecvt_type::state_type)); string from("abcdef"); int res = cdect.length(state, from.data(), from.data() + from.size(), from.size()); CPPUNIT_ASSERT( (size_t)res == from.size() ); } } } catch (runtime_error const&) { } catch (...) { CPPUNIT_FAIL; } #endif } #if !defined (STLPORT) || !(defined (_STLP_NO_WCHAR_T) || !defined (_STLP_USE_EXCEPTIONS)) typedef std::codecvt<wchar_t, char, mbstate_t> my_codecvt_base; class my_codecvt : public my_codecvt_base { public: explicit my_codecvt(size_t r = 0) : my_codecvt_base(r) {} protected: virtual result do_in(state_type& /*state*/, const extern_type* first1, const extern_type* last1, const extern_type*& next1, intern_type* first2, intern_type* last2, intern_type*& next2) const { for ( next1 = first1, next2 = first2; next1 < last1; next1 += 2 ) { if ( (last1 - next1) < 2 || (last2 - next2) < 1 ) return partial; *next2++ = (intern_type)((*(next1 + 1) << 8) | (*next1 & 255)); } return ok; } virtual bool do_always_noconv() const __NO_THROW { return false; } virtual int do_max_length() const __NO_THROW { return 2; } virtual int do_encoding() const __NO_THROW { return 2; } }; #endif void CodecvtTest::imbue_while_reading() { #if !defined (STLPORT) || !(defined (_STLP_NO_WCHAR_T) || !defined (_STLP_USE_EXCEPTIONS)) { wofstream ofs( "test.txt" ); const wchar_t buf[] = L" "; for ( int i = 0; i < 4098; ++i ) { ofs << buf[0]; } } wifstream ifs("test.txt"); // a file containing 4098 wchars ifs.imbue( locale(locale(), new my_codecvt) ); ifs.get(); ifs.seekg(0); ifs.imbue( locale() ); ifs.ignore(4096); int ch = ifs.get(); CPPUNIT_CHECK( ch != (int)WEOF ); #endif } void CodecvtTest::special_encodings() { #if !defined (STLPORT) || (!defined (_STLP_NO_WCHAR_T) && defined (_STLP_USE_EXCEPTIONS)) { locale loc(locale::classic(), new codecvt_byname<wchar_t, char, mbstate_t>("C")); codecvt<wchar_t, char, mbstate_t> const& cvt = use_facet<codecvt<wchar_t, char, mbstate_t> >(loc); mbstate_t state; memset(&state, 0, sizeof(mbstate_t)); char c = '0'; const char *from_next; wchar_t wc; wchar_t *to_next; CPPUNIT_ASSERT( cvt.in(state, &c, &c + 1, from_next, &wc, &wc, to_next) == codecvt_base::ok ); CPPUNIT_ASSERT( to_next == &wc ); CPPUNIT_ASSERT( cvt.in(state, &c, &c + 1, from_next, &wc, &wc + 1, to_next) == codecvt_base::ok ); CPPUNIT_ASSERT( wc == L'0' ); CPPUNIT_ASSERT( to_next == &wc + 1 ); } try { wstring cp936_wstr; const string cp936_str = "\xd6\xd0\xb9\xfa\xc9\xe7\xbb\xe1\xbf\xc6\xd1\xa7\xd4\xba\xb7\xa2\xb2\xbc\x32\x30\x30\x38\xc4\xea\xa1\xb6\xbe\xad\xbc\xc3\xc0\xb6\xc6\xa4\xca\xe9\xa1\xb7\xd6\xb8\xb3\xf6\xa3\xac\x32\x30\x30\x37\xc4\xea\xd6\xd0\xb9\xfa\xbe\xad\xbc\xc3\xd4\xf6\xb3\xa4\xd3\xc9\xc6\xab\xbf\xec\xd7\xaa\xcf\xf2\xb9\xfd\xc8\xc8\xb5\xc4\xc7\xf7\xca\xc6\xc3\xf7\xcf\xd4\xd4\xa4\xbc\xc6\xc8\xab\xc4\xea\x47\x44\x50\xd4\xf6\xcb\xd9\xbd\xab\xb4\xef\x31\x31\x2e\x36\x25\xa1\xa3"; locale loc(locale::classic(), ".936", locale::ctype); codecvt<wchar_t, char, mbstate_t> const& cvt = use_facet<codecvt<wchar_t, char, mbstate_t> >(loc); mbstate_t state; memset(&state, 0, sizeof(mbstate_t)); codecvt_base::result res; { wchar_t wbuf[4096]; // Check we will have enough room for the generated wide string generated from the whole char buffer: int len = cvt.length(state, cp936_str.data(), cp936_str.data() + cp936_str.size(), sizeof(wbuf) / sizeof(wchar_t)); CPPUNIT_ASSERT( cp936_str.size() == (size_t)len ); const char *from_next; wchar_t *to_next; res = cvt.in(state, cp936_str.data(), cp936_str.data() + cp936_str.size(), from_next, wbuf, wbuf + sizeof(wbuf) / sizeof(wchar_t), to_next); CPPUNIT_ASSERT( res == codecvt_base::ok ); CPPUNIT_ASSERT( from_next == cp936_str.data() + cp936_str.size() ); cp936_wstr.assign(wbuf, to_next); } { const wchar_t *from_next; char buf[4096]; char *to_next; res = cvt.out(state, cp936_wstr.data(), cp936_wstr.data() + cp936_wstr.size(), from_next, buf, buf + sizeof(buf), to_next); CPPUNIT_ASSERT( res == codecvt_base::ok ); CPPUNIT_CHECK( string(buf, to_next) == cp936_str ); } } catch (const runtime_error&) { CPPUNIT_MESSAGE("Not enough platform localization support to check 936 code page encoding."); } try { const string utf8_str = "\xe4\xb8\xad\xe5\x9b\xbd\xe7\xa4\xbe\xe4\xbc\x9a\xe7\xa7\x91\xe5\xad\xa6\xe9\x99\xa2\xe5\x8f\x91\xe5\xb8\x83\x32\x30\x30\x38\xe5\xb9\xb4\xe3\x80\x8a\xe7\xbb\x8f\xe6\xb5\x8e\xe8\x93\x9d\xe7\x9a\xae\xe4\xb9\xa6\xe3\x80\x8b\xe6\x8c\x87\xe5\x87\xba\xef\xbc\x8c\x32\x30\x30\x37\xe5\xb9\xb4\xe4\xb8\xad\xe5\x9b\xbd\xe7\xbb\x8f\xe6\xb5\x8e\xe5\xa2\x9e\xe9\x95\xbf\xe7\x94\xb1\xe5\x81\x8f\xe5\xbf\xab\xe8\xbd\xac\xe5\x90\x91\xe8\xbf\x87\xe7\x83\xad\xe7\x9a\x84\xe8\xb6\x8b\xe5\x8a\xbf\xe6\x98\x8e\xe6\x98\xbe\xe9\xa2\x84\xe8\xae\xa1\xe5\x85\xa8\xe5\xb9\xb4\x47\x44\x50\xe5\xa2\x9e\xe9\x80\x9f\xe5\xb0\x86\xe8\xbe\xbe\x31\x31\x2e\x36\x25\xe3\x80\x82"; wstring utf8_wstr; locale loc(locale::classic(), new codecvt_byname<wchar_t, char, mbstate_t>(".utf8")); codecvt<wchar_t, char, mbstate_t> const& cvt = use_facet<codecvt<wchar_t, char, mbstate_t> >(loc); mbstate_t state; memset(&state, 0, sizeof(mbstate_t)); codecvt_base::result res; { wchar_t wbuf[4096]; // Check we will have enough room for the wide string generated from the whole char buffer: int len = cvt.length(state, utf8_str.data(), utf8_str.data() + utf8_str.size(), sizeof(wbuf) / sizeof(wchar_t)); CPPUNIT_ASSERT( utf8_str.size() == (size_t)len ); const char *from_next; wchar_t *to_next; res = cvt.in(state, utf8_str.data(), utf8_str.data() + utf8_str.size(), from_next, wbuf, wbuf + sizeof(wbuf) / sizeof(wchar_t), to_next); CPPUNIT_ASSERT( res == codecvt_base::ok ); CPPUNIT_ASSERT( from_next == utf8_str.data() + utf8_str.size() ); utf8_wstr.assign(wbuf, to_next); // Try to read one char after the other: wchar_t wc; const char* from = utf8_str.data(); const char* from_end = from + utf8_str.size(); from_next = utf8_str.data(); size_t length = 1; size_t windex = 0; while (from + length <= from_end) { res = cvt.in(state, from, from + length, from_next, &wc, &wc + 1, to_next); switch (res) { case codecvt_base::ok: // reset length: from = from_next; length = 1; CPPUNIT_ASSERT( wc == utf8_wstr[windex++] ); wc = 0; break; case codecvt_base::partial: if (from_next == from) // from_next hasn't move so we have to pass more chars ++length; else // char between from and from_next has been eaten, we simply restart // conversion from from_next: from = from_next; continue; case codecvt_base::error: case codecvt_base::noconv: CPPUNIT_FAIL; //break; } } CPPUNIT_ASSERT( windex == utf8_wstr.size() ); } { const wchar_t *from_next; char buf[4096]; char *to_next; res = cvt.out(state, utf8_wstr.data(), utf8_wstr.data() + utf8_wstr.size(), from_next, buf, buf + sizeof(buf), to_next); CPPUNIT_ASSERT( res == codecvt_base::ok ); CPPUNIT_CHECK( string(buf, to_next) == utf8_str ); } { // Check that an obviously wrong UTF8 encoded string is correctly detected: const string bad_utf8_str("\xdf\xdf\xdf\xdf\xdf"); wchar_t wc; const char *from_next; wchar_t *to_next; res = cvt.in(state, bad_utf8_str.data(), bad_utf8_str.data() + bad_utf8_str.size(), from_next, &wc, &wc + 1, to_next); CPPUNIT_ASSERT( res == codecvt_base::error ); } } catch (const runtime_error&) { CPPUNIT_MESSAGE("Not enough platform localization support to check UTF8 encoding."); } #endif } #endif