diff --git a/tinyxml2.cpp b/tinyxml2.cpp index 740f1f5..8a4fa95 100644 --- a/tinyxml2.cpp +++ b/tinyxml2.cpp @@ -18,6 +18,15 @@ static const char CR = CARRIAGE_RETURN; static const char SINGLE_QUOTE = '\''; static const char DOUBLE_QUOTE = '\"'; +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + #define DELETE_NODE( node ) { MemPool* pool = node->memPool; node->~XMLNode(); pool->Free( node ); } #define DELETE_ATTRIBUTE( attrib ) { MemPool* pool = attrib->memPool; attrib->~XMLAttribute(); pool->Free( attrib ); } @@ -117,6 +126,7 @@ char* StrPair::ParseName( char* p ) } + const char* StrPair::GetStr() { if ( flags & NEEDS_FLUSH ) { @@ -124,8 +134,8 @@ const char* StrPair::GetStr() flags ^= NEEDS_FLUSH; if ( flags ) { - char* p = start; - char* q = start; + char* p = start; // the read pointer + char* q = start; // the write pointer while( p < end ) { if ( (flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { @@ -151,21 +161,38 @@ const char* StrPair::GetStr() } else if ( (flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { int i=0; - for( i=0; i( XMLUtil::GetCharacterRef( p, buf, &len ) ); + for( int i=0; i(p); + // Check for BOM: + if ( *(pu+0) == TIXML_UTF_LEAD_0 + && *(pu+1) == TIXML_UTF_LEAD_1 + && *(pu+2) == TIXML_UTF_LEAD_2 ) + { + *bom = true; + p += 3; + } + return p; +} + + +void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) +{ + // Presume an entity, and pull it out. + *length = 0; + + if ( *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + return p + delta + 1; + } + return p+1; +} + + char* XMLDocument::Identify( char* p, XMLNode** node ) { XMLNode* returnNode = 0; @@ -456,14 +610,14 @@ char* XMLText::ParseDeep( char* p ) if ( this->CData() ) { p = value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); if ( !p ) { - document->SetError( XMLDocument::ERROR_PARSING_CDATA, start, 0 ); + document->SetError( ERROR_PARSING_CDATA, start, 0 ); } return p; } else { p = value.ParseText( p, "<", StrPair::TEXT_ELEMENT ); if ( !p ) { - document->SetError( XMLDocument::ERROR_PARSING_TEXT, start, 0 ); + document->SetError( ERROR_PARSING_TEXT, start, 0 ); } if ( p && *p ) { return p-1; @@ -498,7 +652,7 @@ char* XMLComment::ParseDeep( char* p ) const char* start = p; p = value.ParseText( p, "-->", StrPair::COMMENT ); if ( p == 0 ) { - document->SetError( XMLDocument::ERROR_PARSING_COMMENT, start, 0 ); + document->SetError( ERROR_PARSING_COMMENT, start, 0 ); } return p; } @@ -529,7 +683,7 @@ char* XMLDeclaration::ParseDeep( char* p ) const char* start = p; p = value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); if ( p == 0 ) { - document->SetError( XMLDocument::ERROR_PARSING_DECLARATION, start, 0 ); + document->SetError( ERROR_PARSING_DECLARATION, start, 0 ); } return p; } @@ -559,7 +713,7 @@ char* XMLUnknown::ParseDeep( char* p ) p = value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION ); if ( !p ) { - document->SetError( XMLDocument::ERROR_PARSING_UNKNOWN, start, 0 ); + document->SetError( ERROR_PARSING_UNKNOWN, start, 0 ); } return p; } @@ -593,7 +747,7 @@ void XMLAttribute::SetName( const char* n ) int XMLAttribute::QueryIntAttribute( int* value ) const { if ( TIXML_SSCANF( Value(), "%d", value ) == 1 ) - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; return WRONG_ATTRIBUTE_TYPE; } @@ -601,7 +755,7 @@ int XMLAttribute::QueryIntAttribute( int* value ) const int XMLAttribute::QueryUnsignedAttribute( unsigned int* value ) const { if ( TIXML_SSCANF( Value(), "%u", value ) == 1 ) - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; return WRONG_ATTRIBUTE_TYPE; } @@ -613,11 +767,11 @@ int XMLAttribute::QueryBoolAttribute( bool* value ) const if ( ival > 0 || XMLUtil::StringEqual( Value(), "true" ) ) { *value = true; - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; } else if ( ival == 0 || XMLUtil::StringEqual( Value(), "false" ) ) { *value = false; - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; } return WRONG_ATTRIBUTE_TYPE; } @@ -626,7 +780,7 @@ int XMLAttribute::QueryBoolAttribute( bool* value ) const int XMLAttribute::QueryDoubleAttribute( double* value ) const { if ( TIXML_SSCANF( Value(), "%lf", value ) == 1 ) - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; return WRONG_ATTRIBUTE_TYPE; } @@ -634,7 +788,7 @@ int XMLAttribute::QueryDoubleAttribute( double* value ) const int XMLAttribute::QueryFloatAttribute( float* value ) const { if ( TIXML_SSCANF( Value(), "%f", value ) == 1 ) - return ATTRIBUTE_SUCCESS; + return XML_NO_ERROR; return WRONG_ATTRIBUTE_TYPE; } @@ -789,7 +943,7 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement ) while( p ) { p = XMLUtil::SkipWhiteSpace( p ); if ( !p || !(*p) ) { - document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, Name() ); + document->SetError( ERROR_PARSING_ELEMENT, start, Name() ); return 0; } @@ -801,7 +955,7 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement ) p = attrib->ParseDeep( p ); if ( !p ) { DELETE_ATTRIBUTE( attrib ); - document->SetError( XMLDocument::ERROR_PARSING_ATTRIBUTE, start, p ); + document->SetError( ERROR_PARSING_ATTRIBUTE, start, p ); return 0; } LinkAttribute( attrib ); @@ -809,7 +963,7 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement ) // end of the tag else if ( *p == '/' && *(p+1) == '>' ) { if ( closing ) { - document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, p ); + document->SetError( ERROR_PARSING_ELEMENT, start, p ); return 0; } *closedElement = true; @@ -821,7 +975,7 @@ char* XMLElement::ParseAttributes( char* p, bool* closedElement ) break; } else { - document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, p ); + document->SetError( ERROR_PARSING_ELEMENT, start, p ); return 0; } } @@ -875,10 +1029,10 @@ bool XMLElement::Accept( XMLVisitor* visitor ) const } - // --------- XMLDocument ----------- // XMLDocument::XMLDocument() : XMLNode( 0 ), + writeBOM( false ), charBuffer( 0 ) { document = this; // avoid warning about 'this' in initializer list @@ -906,7 +1060,7 @@ XMLDocument::~XMLDocument() void XMLDocument::InitDocument() { - errorID = NO_ERROR; + errorID = XML_NO_ERROR; errorStr1 = 0; errorStr2 = 0; @@ -943,7 +1097,7 @@ XMLText* XMLDocument::NewText( const char* str ) } -int XMLDocument::Load( const char* filename ) +int XMLDocument::LoadFile( const char* filename ) { ClearChildren(); InitDocument(); @@ -953,13 +1107,13 @@ int XMLDocument::Load( const char* filename ) SetError( ERROR_FILE_NOT_FOUND, filename, 0 ); return errorID; } - Load( fp ); + LoadFile( fp ); fclose( fp ); return errorID; } -int XMLDocument::Load( FILE* fp ) +int XMLDocument::LoadFile( FILE* fp ) { ClearChildren(); InitDocument(); @@ -968,16 +1122,27 @@ int XMLDocument::Load( FILE* fp ) unsigned size = ftell( fp ); fseek( fp, 0, SEEK_SET ); + if ( size == 0 ) { + return errorID; + } + charBuffer = new char[size+1]; fread( charBuffer, size, 1, fp ); charBuffer[size] = 0; - ParseDeep( charBuffer ); + const char* p = charBuffer; + p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::ReadBOM( p, &writeBOM ); + if ( !p || !*p ) { + return 0; // correctly parse an empty string? + } + + ParseDeep( charBuffer + (p-charBuffer) ); return errorID; } -void XMLDocument::Save( const char* filename ) +void XMLDocument::SaveFile( const char* filename ) { FILE* fp = fopen( filename, "w" ); XMLStreamer stream( fp ); @@ -994,10 +1159,17 @@ int XMLDocument::Parse( const char* p ) if ( !p || !*p ) { return true; // correctly parse an empty string? } + p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::ReadBOM( p, &writeBOM ); + if ( !p || !*p ) { + return true; // correctly parse an empty string? + } + size_t len = strlen( p ); charBuffer = new char[ len+1 ]; memcpy( charBuffer, p, len+1 ); + ParseDeep( charBuffer ); return errorID; } @@ -1063,6 +1235,7 @@ XMLStreamer::XMLStreamer( FILE* file ) : } restrictedEntityFlag['&'] = true; restrictedEntityFlag['<'] = true; + restrictedEntityFlag['>'] = true; // not required, but consistency is nice buffer.Push( 0 ); } @@ -1115,7 +1288,8 @@ void XMLStreamer::PrintString( const char* p, bool restricted ) const bool* flag = restricted ? restrictedEntityFlag : entityFlag; while ( *q ) { - if ( *q < ENTITY_RANGE ) { + // Remember, char is sometimes signed. (How many times has that bitten me?) + if ( *q > 0 && *q < ENTITY_RANGE ) { // Check for entities. If one is found, flush // the stream up until the entity, write the // entity, and keep looking. @@ -1143,6 +1317,18 @@ void XMLStreamer::PrintString( const char* p, bool restricted ) } +void XMLStreamer::PushHeader( bool writeBOM, bool writeDec ) +{ + static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; + if ( writeBOM ) { + Print( "%s", bom ); + } + if ( writeDec ) { + PushDeclaration( "xml version=\"1.0\"" ); + } +} + + void XMLStreamer::OpenElement( const char* name ) { if ( elementJustOpened ) { @@ -1262,6 +1448,15 @@ void XMLStreamer::PushUnknown( const char* value ) } +bool XMLStreamer::VisitEnter( const XMLDocument& doc ) +{ + if ( doc.HasBOM() ) { + PushHeader( true, false ); + } + return true; +} + + bool XMLStreamer::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) { OpenElement( element.Name() ); diff --git a/tinyxml2.h b/tinyxml2.h index 4792b09..a1f7603 100644 --- a/tinyxml2.h +++ b/tinyxml2.h @@ -371,6 +371,12 @@ public: inline static int IsUTF8Continuation( unsigned char p ) { return p & 0x80; } inline static int IsAlphaNum( unsigned char anyByte ) { return ( anyByte < 128 ) ? isalnum( anyByte ) : 1; } inline static int IsAlpha( unsigned char anyByte ) { return ( anyByte < 128 ) ? isalpha( anyByte ) : 1; } + + static const char* ReadBOM( const char* p, bool* hasBOM ); + // p is the starting location, + // the UTF-8 value of the entity will be placed in value, and length filled in. + static const char* GetCharacterRef( const char* p, char* value, int* length ); + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); }; @@ -567,9 +573,21 @@ protected: enum { - ATTRIBUTE_SUCCESS, + XML_NO_ERROR = 0, + NO_ATTRIBUTE, - WRONG_ATTRIBUTE_TYPE + WRONG_ATTRIBUTE_TYPE, + + ERROR_FILE_NOT_FOUND, + ERROR_ELEMENT_MISMATCH, + ERROR_PARSING_ELEMENT, + ERROR_PARSING_ATTRIBUTE, + ERROR_IDENTIFYING_TAG, + ERROR_PARSING_TEXT, + ERROR_PARSING_CDATA, + ERROR_PARSING_COMMENT, + ERROR_PARSING_DECLARATION, + ERROR_PARSING_UNKNOWN }; @@ -691,9 +709,11 @@ public: virtual const XMLDocument* ToDocument() const { return this; } int Parse( const char* xml ); - int Load( const char* filename ); - int Load( FILE* ); - void Save( const char* filename ); + int LoadFile( const char* filename ); + int LoadFile( FILE* ); + void SaveFile( const char* filename ); + + bool HasBOM() const { return writeBOM; } void Print( XMLStreamer* streamer=0 ); virtual bool Accept( XMLVisitor* visitor ) const; @@ -716,24 +736,10 @@ public: */ void DeleteNode( XMLNode* node ) { node->parent->DeleteChild( node ); } - enum { - NO_ERROR = 0, - ERROR_FILE_NOT_FOUND, - ERROR_ELEMENT_MISMATCH, - ERROR_PARSING_ELEMENT, - ERROR_PARSING_ATTRIBUTE, - ERROR_IDENTIFYING_TAG, - ERROR_PARSING_TEXT, - ERROR_PARSING_CDATA, - ERROR_PARSING_COMMENT, - ERROR_PARSING_DECLARATION, - ERROR_PARSING_UNKNOWN - - }; void SetError( int error, const char* str1, const char* str2 ); - bool Error() const { return errorID != NO_ERROR; } - int GetErrorID() const { return errorID; } + bool Error() const { return errorID != XML_NO_ERROR; } + int ErrorID() const { return errorID; } const char* GetErrorStr1() const { return errorStr1; } const char* GetErrorStr2() const { return errorStr2; } void PrintError() const; @@ -745,6 +751,7 @@ private: void operator=( const XMLDocument& ); // not supported void InitDocument(); + bool writeBOM; int errorID; const char* errorStr1; const char* errorStr2; @@ -763,6 +770,7 @@ public: XMLStreamer( FILE* file=0 ); ~XMLStreamer() {} + void PushHeader( bool writeBOM, bool writeDeclaration ); void OpenElement( const char* name ); void PushAttribute( const char* name, const char* value ); void CloseElement(); @@ -772,7 +780,7 @@ public: void PushDeclaration( const char* value ); void PushUnknown( const char* value ); - virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { return true; } + virtual bool VisitEnter( const XMLDocument& /*doc*/ ); virtual bool VisitExit( const XMLDocument& /*doc*/ ) { return true; } virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ); diff --git a/xmltest.cpp b/xmltest.cpp index c025841..95f9738 100644 --- a/xmltest.cpp +++ b/xmltest.cpp @@ -86,6 +86,18 @@ bool XMLTest( const char* testString, int expected, int found, bool echo=true ) } +void NullLineEndings( char* p ) +{ + while( p && *p ) { + if ( *p == '\n' || *p == '\r' ) { + *p = 0; + return; + } + ++p; + } +} + + int main( int argc, const char* argv ) { #if defined( WIN32 ) @@ -150,6 +162,7 @@ int main( int argc, const char* argv ) } #endif { +#if 0 // Test: Programmatic DOM // Build: // @@ -198,17 +211,19 @@ int main( int argc, const char* argv ) printf( "%s", streamer.CStr() ); delete doc; +#endif } #endif { +#if 0 // Test: Dream // XML1 : 1,187,569 bytes in 31,209 allocations // XML2 : 469,073 bytes in 323 allocations //int newStart = gNew; XMLDocument doc; - doc.Load( "dream.xml" ); + doc.LoadFile( "dream.xml" ); - doc.Save( "dreamout.xml" ); + doc.SaveFile( "dreamout.xml" ); doc.PrintError(); XMLTest( "Dream", "xml version=\"1.0\"", @@ -222,7 +237,7 @@ int main( int argc, const char* argv ) doc.LastChild()->LastChild()->LastChild()->LastChild()->LastChildElement()->GetText() ); XMLDocument doc2; - doc2.Load( "dreamout.xml" ); + doc2.LoadFile( "dreamout.xml" ); XMLTest( "Dream-out", "xml version=\"1.0\"", doc2.FirstChild()->ToDeclaration()->Value() ); XMLTest( "Dream-out", true, doc2.FirstChild()->NextSibling()->ToUnknown() ? true : false ); @@ -231,10 +246,132 @@ int main( int argc, const char* argv ) XMLTest( "Dream-out", "And Robin shall restore amends.", doc2.LastChild()->LastChild()->LastChild()->LastChild()->LastChildElement()->GetText() ); +#endif //gNewTotal = gNew - newStart; } - #if defined( WIN32 ) +#if 0 + { + const char* error = "\n" + "\n" + " \n" + ""; + + XMLDocument doc; + doc.Parse( error ); + XMLTest( "Bad XML", doc.ErrorID(), ERROR_PARSING_ATTRIBUTE ); + } + + { + const char* str = ""; + + XMLDocument doc; + doc.Parse( str ); + + XMLElement* ele = doc.FirstChildElement(); + + int iVal, result; + double dVal; + + result = ele->QueryDoubleAttribute( "attr0", &dVal ); + XMLTest( "Query attribute: int as double", result, XML_NO_ERROR ); + XMLTest( "Query attribute: int as double", (int)dVal, 1 ); + result = ele->QueryDoubleAttribute( "attr1", &dVal ); + XMLTest( "Query attribute: double as double", (int)dVal, 2 ); + result = ele->QueryIntAttribute( "attr1", &iVal ); + XMLTest( "Query attribute: double as int", result, XML_NO_ERROR ); + XMLTest( "Query attribute: double as int", iVal, 2 ); + result = ele->QueryIntAttribute( "attr2", &iVal ); + XMLTest( "Query attribute: not a number", result, WRONG_ATTRIBUTE_TYPE ); + result = ele->QueryIntAttribute( "bar", &iVal ); + XMLTest( "Query attribute: does not exist", result, NO_ATTRIBUTE ); + } + + { + const char* str = ""; + + XMLDocument doc; + doc.Parse( str ); + + XMLElement* ele = doc.FirstChildElement(); + + int iVal; + double dVal; + + ele->SetAttribute( "str", "strValue" ); + ele->SetAttribute( "int", 1 ); + ele->SetAttribute( "double", -1.0 ); + + const char* cStr = ele->Attribute( "str" ); + ele->QueryIntAttribute( "int", &iVal ); + ele->QueryDoubleAttribute( "double", &dVal ); + + XMLTest( "Attribute round trip. c-string.", "strValue", cStr ); + XMLTest( "Attribute round trip. int.", 1, iVal ); + XMLTest( "Attribute round trip. double.", -1, (int)dVal ); + } + +#endif + { + XMLDocument doc; + doc.LoadFile( "utf8test.xml" ); + + // Get the attribute "value" from the "Russian" element and check it. + XMLElement* element = doc.FirstChildElement( "document" )->FirstChildElement( "Russian" ); + const unsigned char correctValue[] = { 0xd1U, 0x86U, 0xd0U, 0xb5U, 0xd0U, 0xbdU, 0xd0U, 0xbdU, + 0xd0U, 0xbeU, 0xd1U, 0x81U, 0xd1U, 0x82U, 0xd1U, 0x8cU, 0 }; + + XMLTest( "UTF-8: Russian value.", (const char*)correctValue, element->Attribute( "value" ) ); + + const unsigned char russianElementName[] = { 0xd0U, 0xa0U, 0xd1U, 0x83U, + 0xd1U, 0x81U, 0xd1U, 0x81U, + 0xd0U, 0xbaU, 0xd0U, 0xb8U, + 0xd0U, 0xb9U, 0 }; + const char russianText[] = "<\xD0\xB8\xD0\xBC\xD0\xB5\xD0\xB5\xD1\x82>"; + + XMLText* text = doc.FirstChildElement( "document" )->FirstChildElement( (const char*) russianElementName )->FirstChild()->ToText(); + XMLTest( "UTF-8: Browsing russian element name.", + russianText, + text->Value() ); + + // Now try for a round trip. + doc.SaveFile( "utf8testout.xml" ); + + // Check the round trip. + char savedBuf[256]; + char verifyBuf[256]; + int okay = 0; + + FILE* saved = fopen( "utf8testout.xml", "r" ); + FILE* verify = fopen( "utf8testverify.xml", "r" ); + + if ( saved && verify ) + { + okay = 1; + while ( fgets( verifyBuf, 256, verify ) ) + { + fgets( savedBuf, 256, saved ); + NullLineEndings( verifyBuf ); + NullLineEndings( savedBuf ); + + if ( strcmp( verifyBuf, savedBuf ) ) + { + printf( "verify:%s<\n", verifyBuf ); + printf( "saved :%s<\n", savedBuf ); + okay = 0; + break; + } + } + } + if ( saved ) + fclose( saved ); + if ( verify ) + fclose( verify ); + XMLTest( "UTF-8: Verified multi-language round trip.", 1, okay ); + } + + +#if defined( WIN32 ) _CrtMemCheckpoint( &endMemState ); //_CrtMemDumpStatistics( &endMemState );