00001 // $Id: makemsg.cpp 1333 2006-06-16 20:34:16Z alex $ 00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE 00003 ================================XARAHEADERSTART=========================== 00004 00005 Xara LX, a vector drawing and manipulation program. 00006 Copyright (C) 1993-2006 Xara Group Ltd. 00007 Copyright on certain contributions may be held in joint with their 00008 respective authors. See AUTHORS file for details. 00009 00010 LICENSE TO USE AND MODIFY SOFTWARE 00011 ---------------------------------- 00012 00013 This file is part of Xara LX. 00014 00015 Xara LX is free software; you can redistribute it and/or modify it 00016 under the terms of the GNU General Public License version 2 as published 00017 by the Free Software Foundation. 00018 00019 Xara LX and its component source files are distributed in the hope 00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the 00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 00022 See the GNU General Public License for more details. 00023 00024 You should have received a copy of the GNU General Public License along 00025 with Xara LX (see the file GPL in the root directory of the 00026 distribution); if not, write to the Free Software Foundation, Inc., 51 00027 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 00028 00029 00030 ADDITIONAL RIGHTS 00031 ----------------- 00032 00033 Conditional upon your continuing compliance with the GNU General Public 00034 License described above, Xara Group Ltd grants to you certain additional 00035 rights. 00036 00037 The additional rights are to use, modify, and distribute the software 00038 together with the wxWidgets library, the wxXtra library, and the "CDraw" 00039 library and any other such library that any version of Xara LX relased 00040 by Xara Group Ltd requires in order to compile and execute, including 00041 the static linking of that library to XaraLX. In the case of the 00042 "CDraw" library, you may satisfy obligation under the GNU General Public 00043 License to provide source code by providing a binary copy of the library 00044 concerned and a copy of the license accompanying it. 00045 00046 Nothing in this section restricts any of the rights you have under 00047 the GNU General Public License. 00048 00049 00050 SCOPE OF LICENSE 00051 ---------------- 00052 00053 This license applies to this program (XaraLX) and its constituent source 00054 files only, and does not necessarily apply to other Xara products which may 00055 in part share the same code base, and are subject to their own licensing 00056 terms. 00057 00058 This license does not apply to files in the wxXtra directory, which 00059 are built into a separate library, and are subject to the wxWindows 00060 license contained within that directory in the file "WXXTRA-LICENSE". 00061 00062 This license does not apply to the binary libraries (if any) within 00063 the "libs" directory, which are subject to a separate license contained 00064 within that directory in the file "LIBS-LICENSE". 00065 00066 00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS 00068 ---------------------------------------------- 00069 00070 Subject to the terms of the GNU Public License (see above), you are 00071 free to do whatever you like with your modifications. However, you may 00072 (at your option) wish contribute them to Xara's source tree. You can 00073 find details of how to do this at: 00074 http://www.xaraxtreme.org/developers/ 00075 00076 Prior to contributing your modifications, you will need to complete our 00077 contributor agreement. This can be found at: 00078 http://www.xaraxtreme.org/developers/contribute/ 00079 00080 Please note that Xara will not accept modifications which modify any of 00081 the text between the start and end of this header (marked 00082 XARAHEADERSTART and XARAHEADEREND). 00083 00084 00085 MARKS 00086 ----- 00087 00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara 00089 designs are registered or unregistered trademarks, design-marks, and/or 00090 service marks of Xara Group Ltd. All rights in these marks are reserved. 00091 00092 00093 Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. 00094 http://www.xara.com/ 00095 00096 =================================XARAHEADEREND============================ 00097 */ 00098 /************************************************************************************* 00099 00100 00101 Author: Justin_Flude (Xara Group Ltd) <camelotdev@xara.com> 00102 00103 Definition of the member function StringBase::MakeMsg(), which extends the traditional 00104 sprintf() function to cope with arbitrarily ordered format specifications. 00105 **************************************************************************************/ 00106 00107 #include "camtypes.h" 00108 //#include "basestr.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00109 //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00110 00111 00112 /************************************************************************************** 00113 > INT32 __cdecl StringBase::MakeMsg(UINT32 resourceID ...) 00114 00115 Author: Justin_Flude (Xara Group Ltd) <camelotdev@xara.com> 00116 Created: 22nd April 1993 00117 Inputs: A string resource identifier, and a variable number of other parameters, 00118 which must match the format specifiers embedded in the string resource. 00119 Note that floating point parameters are NOT supported. 00120 Outputs: Changes "this" to become the formatted string, with the passed parameters 00121 substituted into the string resource in the correct order. 00122 Returns: The number of characters in the formatted string. This will be zero if 00123 a problem occurs, such as not being able to load the format string 00124 resource. 00125 Purpose: Internationally portable version of sprintf(...), eg. 00126 StringBase s; 00127 INT32 x, y, z; 00128 s.MakeMsg(_R(IDS_COORDFORMAT), x, y, z); 00129 TextOut(hdc, 20, 20, s, s.Length()); 00130 Errors: ENSURE failure if called for a String that hasn't been allocated. 00131 ***************************************************************************************/ 00132 INT32 __cdecl StringBase::MakeMsg(UINT32 resID ...) 00133 { 00134 /* if (IsUserName("JustinF")) 00135 TRACE( _T("MakeMsg called with resource ID %u\n"), resID); 00136 */ 00137 ENSURE(text, "Call to StringBase::MakeMsg for unallocated String"); 00138 00139 va_list ap; 00140 va_start(ap, resID); 00141 00142 INT32 n = 0; 00143 StringBase s; 00144 if (s.Alloc(MAX_STRING_RES_LENGTH) && s.Load(resID)) 00145 { 00146 n = CCvsprintf(s.text, ap); 00147 } 00148 #ifdef _DEBUG 00149 /* else 00150 { 00151 if (IsUserName("JustinF")) 00152 TRACE( _T("Failed to allocate or load the format string in MakeMsg()\n")); 00153 } */ 00154 #endif 00155 00156 va_end(ap); 00157 00158 #if !defined(EXCLUDE_FROM_RALPH) && !defined(EXCLUDE_FROM_XARALX) 00159 // Remember the resource ID, in case the help system can't work out the message ID. 00160 SetNextMsgHelpContext(resID); 00161 #endif 00162 00163 // Return the number of characters in the formatted string. 00164 return n; 00165 } 00166 00167 00168 00169 00170 /************************************************************************************** 00171 > INT32 __cdecl StringBase::_MakeMsg(const TCHAR* fmt ...) 00172 00173 Author: Justin_Flude (Xara Group Ltd) <camelotdev@xara.com> 00174 Created: 22nd April 1993 00175 Inputs: A constant character pointer, and a variable number of other parameters, 00176 which must match the format specifiers embedded in the character pointer. 00177 **** Note that floating point parameters are NOT supported **** 00178 Outputs: Changes "this" to become the formatted string, with the passed parameters 00179 substituted into the string resource in the correct order. 00180 Returns: The number of characters in the formatted string. 00181 Purpose: Internationally portable version of sprintf(...), eg. 00182 UINT32 x; 00183 s._MakeMsg("#1%lu", x); 00184 TextOut(hdc, 20, 20, s, s.Length()); 00185 ***************************************************************************************/ 00186 INT32 __cdecl StringBase::_MakeMsg(const TCHAR* fmt ...) 00187 { 00188 /* if (IsUserName("JustinF")) 00189 TRACE( _T("_MakeMsg called with format string %s\n"), fmt); 00190 */ 00191 ENSURE(text, "Call to StringBase::_MakeMsg for an unallocated String"); 00192 ENSURE(fmt, "Call to StringBase::_MakeMsg with a null format parameter"); 00193 00194 va_list ap; 00195 va_start(ap, fmt); 00196 00197 INT32 n = CCvsprintf(fmt, ap); 00198 00199 va_end(ap); 00200 return n; 00201 } 00202 00203 00204 00206 // 00207 // I M P L E M E N T A T I O N 00208 // 00209 // The types that can appear in a formatting string. These have been documented, 00210 // but it's worth pointing out that the wsprintf(...) function will accept other 00211 // %specifiers, eg. %x, which are not documented. 00212 00213 enum ArgType 00214 { 00215 LITERAL, CHAR_ARG, SIGNED_INT_ARG, SIGNED_INT32_ARG, UNSIGNED_INT_ARG, 00216 UNSIGNED_INT32_ARG, CHAR_POINTER_ARG, STRING_POINTER_ARG, UINT_PTR_ARG 00217 }; 00218 00219 00220 00221 // Private helper data structure - a linked list of strings & associated 00222 // type information. 00223 struct Item 00224 { 00225 TCHAR* str; // pointer to literal / format specifier 00226 ArgType type; // type of associated parameter 00227 INT32 pos; // relative position in format string 00228 Item* next; // next in list 00229 00230 static Item* head; 00231 static Item* tail; 00232 00233 Item(const TCHAR* s, INT32 ps, ArgType t); 00234 ~Item(); 00235 }; 00236 00237 00238 Item* Item::head = 0; 00239 Item* Item::tail = 0; 00240 00241 00242 // Create & join an Item. 00243 Item::Item(const TCHAR* s, INT32 ps, ArgType t) 00244 { 00245 camStrcpy(str = new TCHAR[camStrlen(s) + 1], s); 00246 pos = ps; 00247 type = t; 00248 next = 0; 00249 if (tail) tail = tail->next = this; 00250 else head = tail = this; 00251 } 00252 00253 00254 00255 00256 // Destroy an Item - tail recursively deletes every Item, if called by 00257 // delete Item::head; 00258 Item::~Item() 00259 { 00260 delete[] str; 00261 if (next) delete next; 00262 else head = tail = 0; 00263 } 00264 00265 00266 00267 00268 /************************************************************************************** 00269 > INT32 StringBase::BuildList(const TCHAR* format) 00270 00271 Author: Justin_Flude (Xara Group Ltd) <camelotdev@xara.com> 00272 Created: 22nd April 1993 00273 Inputs: A pointer to the format string to be analysed. 00274 Returns: The number of format specifiers in the string, which should be the same 00275 as the number of arguments to be formatted. 00276 Purpose: This private helper function scans the layout string, breaking it up into 00277 literal text and parameters, which are placed on an private list in the 00278 order they are encountered, together with their type and which argument 00279 passed to Format that they refer to. The formatting must be done in two 00280 passes as the order of the parameters is not known until the whole format 00281 string has been scanned. 00282 Scope: Private 00283 ***************************************************************************************/ 00284 INT32 StringBase::BuildList(const TCHAR* format) 00285 { 00286 /* if (IsUserName("JustinF")) 00287 TRACE( _T("\tBuildList called with format string %s\n"), format); 00288 */ 00289 ENSURE(format, "Null parameter passed to StringBase::BuildList"); 00290 const TCHAR* start = format; 00291 INT32 nArgs = 0; 00292 while (*start) 00293 { 00294 const TCHAR* next = start; 00295 // Look for a format specifier in the layout string 00296 if (*next == TEXT('#')) 00297 if (IsNumeric(*(++next))) 00298 { 00299 INT32 ArgPos = *next - TEXT('0'); // MS approved method - yuck!!! 00300 ENSURE(ArgPos >= 1 && ArgPos <= 9, 00301 "Illegal format string passed to MakeMsg!"); 00302 00303 if (*(++next) == TEXT('%')) 00304 { 00305 // Seem to have found the beginning of a specifier, 00306 // so try parsing it, to extract the type information 00307 if (*(++next) == TEXT('-')) next++; 00308 if (*next == TEXT('#')) next++; 00309 if (*next == TEXT('0')) next++; 00310 while (IsNumeric(*next)) next++; 00311 if (*next == TEXT('.')) 00312 while (IsNumeric(*++next)); 00313 00314 // Ok, we have skipped the width, precision etc. and 00315 // "next" should now be pointing at the type character(s) 00316 ArgType kind; 00317 if (*next == TEXT('l')) // an INT32 something or another? 00318 switch (*++next) 00319 { 00320 case TEXT('d'): case TEXT('i'): 00321 kind = SIGNED_INT32_ARG; 00322 break; 00323 case TEXT('u'): case TEXT('x'): case TEXT('X'): 00324 kind = UNSIGNED_INT32_ARG; 00325 break; 00326 default: // false alarm, can't be a specifier 00327 next = start + 1; 00328 goto not_format; 00329 } 00330 else 00331 switch (*next) // single type character 00332 { 00333 case TEXT('c'): 00334 kind = CHAR_ARG; 00335 break; 00336 case TEXT('d'): case TEXT('i'): 00337 kind = SIGNED_INT_ARG; 00338 break; 00339 case TEXT('p'): // a pointer 00340 kind = UINT_PTR_ARG; 00341 break; 00342 case TEXT('u'): case TEXT('x'): case TEXT('X'): 00343 kind = UNSIGNED_INT_ARG; 00344 break; 00345 case TEXT('s'): 00346 kind = CHAR_POINTER_ARG; 00347 break; 00348 case TEXT('S'): 00349 kind = STRING_POINTER_ARG; 00350 break; 00351 default: 00352 next = start + 1; 00353 goto not_format; // boo hiss hooray! 00354 } 00355 00356 // Successfully parsed the format specifier, so add 00357 // it to the Item list. Skip the leading #n of the specifier, 00358 // so (next) now points to the character following the '%X' 00359 // format specifier. 00360 next++; 00361 00362 // Extract the specifier. 00363 TCHAR temp[64]; 00364 INT32 len = next - start - 2; 00365 camStrncpy(temp, start + 2, len); 00366 temp[len] = 0; 00367 new Item(temp, ArgPos, kind); 00368 start = next; 00369 nArgs++; 00370 00371 /* if (IsUserName("JustinF")) 00372 { 00373 TRACE( _T("\tItem: (%d) at #%d %s\n"), kind, ArgPos, temp); 00374 TRACE( _T("\t\tRemaining: %s\n"), start); 00375 } 00376 */ 00377 continue; 00378 } 00379 else 00380 // Put back the previous character 00381 next--; 00382 } 00383 00384 not_format: 00385 // Scan a literal up to the next '#' or null, whichever is sooner 00386 while (*next && *next != TEXT('#')) next++; 00387 00388 TCHAR temp[256]; 00389 INT32 len = next - start; 00390 00391 camStrncpy(temp, start, len); 00392 temp[len] = 0; 00393 new Item(temp, 0, LITERAL); 00394 start = next; 00395 00396 /* if (IsUserName("JustinF")) 00397 { 00398 TRACE( _T("\tLiteral: %s\n"), temp); 00399 } 00400 */ 00401 } 00402 00403 return nArgs; 00404 } 00405 00406 00407 00408 00409 /************************************************************************************** 00410 > INT32 StringBase::CCvsprintf(const TCHAR* layout, va_list va) 00411 00412 Author: Justin_Flude (Xara Group Ltd) <camelotdev@xara.com> 00413 Created: 22nd April 1993 00414 Inputs: A pointer to the format string, a variable-parameter pointer to the 00415 arguments to be formatted. 00416 Returns: The length of the StringBase once the arguments have been substituted into 00417 it. 00418 Purpose: This private helper function takes the list generated by 00419 StringBase::BuildList and scans through it, formatting stack-frame 00420 parameters according to their list entry. This produces another list, 00421 this time of formatted parameters, which is then concatenated to 00422 produce the final output. 00423 Scope: Private 00424 ***************************************************************************************/ 00425 INT32 StringBase::CCvsprintf(const TCHAR* layout, va_list va) 00426 { 00427 /* if (IsUserName("JustinF")) 00428 TRACE( _T("CCvsprintf called with format string %s\n"), layout); 00429 */ 00430 INT32 n = BuildList(layout); 00431 for (INT32 i = 1; i <= n; i++) 00432 { 00433 for (Item* p = Item::head; p; p = p->next) 00434 if (p->pos != i) 00435 continue; 00436 else 00437 { // Found specifier i in the list, so grab from the stack 00438 // and call wsprintf() to convert to text. The converted 00439 // text is put in the temp[] buffer - note the enormous 00440 // size of this, in accordance with the C standard. 00441 TCHAR temp[512]; 00442 switch (p->type) 00443 { 00444 // type 'c' - single character 00445 case CHAR_ARG: 00446 #if defined(__WXMSW__) 00447 camSnprintf( temp, 512, p->str, va_arg(va, TCHAR) ); 00448 #else // TCHARs are promoted to INT32 when passed via ... under GCC 00449 camSnprintf( temp, 512, p->str, va_arg(va, INT32) ); 00450 #endif 00451 break; 00452 // type 'd' / 'i' - signed decimal integer 00453 case SIGNED_INT_ARG: 00454 camSnprintf(temp, 512, p->str, va_arg(va, INT32)); 00455 break; 00456 // type 'ld' / 'li' - signed decimal INT32 00457 case SIGNED_INT32_ARG: 00458 camSnprintf(temp, 512, p->str, va_arg(va, INT32)); 00459 break; 00460 // type 'u' - unsigned decimal integer 00461 case UNSIGNED_INT_ARG: 00462 camSnprintf(temp, 512, p->str, va_arg(va, UINT32)); 00463 break; 00464 // type 'lu' / 'lx' / 'lX' - unsigned decimal INT32 00465 case UNSIGNED_INT32_ARG: 00466 camSnprintf(temp, 512, p->str, va_arg(va, UINT32)); 00467 break; 00468 // type 's' - long pointer to array of (constant) char 00469 case CHAR_POINTER_ARG: 00470 camSnprintf(temp, 512, p->str, va_arg(va, LPCTSTR)); 00471 break; 00472 // type 'p' a pointer 00473 case UINT_PTR_ARG: 00474 camSnprintf(temp, 512, p->str, va_arg(va, UINT_PTR)); 00475 break; 00476 // type 'S' - a pointer to a StringBase. First change the 00477 // %S format specifier, which is our own, into a sprintf() 00478 // compatible %s. Note that the 'S' is always the last 00479 // character of the format specifier. 00480 case STRING_POINTER_ARG: 00481 (p->str)[camStrlen(p->str) - 1] = TEXT('s'); 00482 camSnprintf(temp, 512, p->str, LPCTSTR(va_arg(va, const StringBase*)->text)); 00483 break; 00484 default: 00485 break; 00486 } 00487 // Replace the format specifier with the formatted text 00488 delete[] p->str; 00489 camStrcpy(p->str = new TCHAR[camStrlen(temp) + 1], temp); 00490 break; 00491 } 00492 } 00493 00494 // Concatenate every string in the list, tidy up, and return in "this" string 00495 *text = 0; 00496 UINT32 NextChar = 0; 00497 INT32 Index = 0; 00498 for (Item* p = Item::head; (p != NULL && NextChar < length) ; p = p->next) 00499 { 00500 /* if (camStrlen(text) + camStrlen(p->str) < length) 00501 camStrcat(text, p->str); 00502 else 00503 { 00504 ENSURE(FALSE, "Call to String::MakeMsg will overflow - text has been truncated"); 00505 } 00506 */ 00507 for (Index = 0; p->str[Index] != 0 && NextChar < length; Index++) 00508 text[NextChar++] = p->str[Index]; 00509 } 00510 00511 if (NextChar > length) 00512 NextChar = length; 00513 text[NextChar] = TCHAR('\0'); 00514 00515 delete Item::head; 00516 return camStrlen(text); 00517 }