Annotation of parser3/src/main/pa_charset.C, revision 1.9
1.1 paf 1: /** @file
2: Parser: Charset connection implementation.
3:
1.4 paf 4: Copyright(c) 2001 ArtLebedev Group(http://www.artlebedev.com)
5: Author: Alexander Petrosyan<paf@design.ru>(http://paf.design.ru)
1.1 paf 6:
1.9 ! paf 7: $Id: pa_charset.C,v 1.8 2001/12/27 19:57:09 paf Exp $
1.1 paf 8: */
9:
10: #include "pa_charset.h"
11:
12: #ifdef XML
1.8 paf 13: #include "libxml/encoding.h"
1.1 paf 14: #endif
15:
16: // globals
17:
18:
19: // consts
20:
21: #define MAX_CHARSET_UNI_CODES 500
22:
23: // helpers
24:
25: inline void prepare_case_tables(unsigned char *tables) {
26: unsigned char *lcc_table=tables+lcc_offset;
27: unsigned char *fcc_table=tables+fcc_offset;
28: for(int i=0; i<0x100; i++)
29: lcc_table[i]=fcc_table[i]=i;
30: }
31: inline void cstr2ctypes(unsigned char *tables, const unsigned char *cstr,
32: unsigned char bit) {
33: unsigned char *ctypes_table=tables+ctypes_offset;
34: ctypes_table[0]=bit;
35: for(; *cstr; cstr++) {
36: unsigned char c=*cstr;
37: ctypes_table[c]|=bit;
38: }
39: }
40: inline unsigned int to_wchar_code(const char *cstr) {
41: if(!cstr || !*cstr)
42: return 0;
43: if(cstr[1]==0)
1.4 paf 44: return(unsigned int)(unsigned char)cstr[0];
1.1 paf 45:
46: char *error_pos;
1.4 paf 47: return(unsigned int)strtol(cstr, &error_pos, 0);
1.1 paf 48: }
49: inline bool to_bool(const char *cstr) {
50: return cstr && *cstr!=0;
51: }
52: static void element2ctypes(unsigned char c, bool belongs,
53: unsigned char *tables, unsigned char bit, int group_offset=-1) {
54: if(!belongs)
55: return;
56:
57: unsigned char *ctypes_table=tables+ctypes_offset;
58:
59: ctypes_table[c]|=bit;
60: if(group_offset>=0)
1.4 paf 61: tables[cbits_offset+group_offset+c/8] |= 1<<(c%8);
1.1 paf 62: }
63: static void element2case(unsigned char from, unsigned char to,
64: unsigned char *tables) {
65: if(!to)
66: return;
67:
68: unsigned char *lcc_table=tables+lcc_offset;
69: unsigned char *fcc_table=tables+fcc_offset;
70: lcc_table[from]=to;
71: fcc_table[from]=to; fcc_table[to]=from;
72: }
73:
1.8 paf 74: /// @test custom encodings
1.1 paf 75: #ifdef XML
1.8 paf 76: /*
1.1 paf 77: template <class TType> class ENameMapFor2 : public ENameMap {
1.4 paf 78: public:
1.1 paf 79: // -----------------------------------------------------------------------
80: // Constructors and Destructor
81: // -----------------------------------------------------------------------
82: ENameMapFor2(
83: const XMLCh* const encodingName
84: , const XMLCh* const fromTable
85: , const XMLTransService::TransRec* const toTable
86: , const unsigned int toTableSize
1.4 paf 87: ): ENameMap(encodingName),
1.1 paf 88: ffromTable(fromTable),
89: ftoTable(toTable),
90: ftoTableSize(toTableSize) {}
91:
92: // -----------------------------------------------------------------------
93: // Implementation of virtual factory method
94: // -----------------------------------------------------------------------
95: virtual XMLTranscoder* makeNew(const unsigned int blockSize) const {
96: return new TType(
97: getKey(),
98: blockSize,
99: ffromTable,
100: ftoTable, ftoTableSize);
101: }
102: private:
103: const XMLCh* const ffromTable;
104: const XMLTransService::TransRec* const ftoTable;
105: const unsigned int ftoTableSize;
106:
1.4 paf 107: private:
1.1 paf 108: // -----------------------------------------------------------------------
109: // Unimplemented constructors and operators
110: // -----------------------------------------------------------------------
111: ENameMapFor2();
112: ENameMapFor2(const ENameMapFor2<TType>&);
113: void operator=(const ENameMapFor2<TType>&);
114: };
115:
116: class XML256TableTranscoder2 : public XML256TableTranscoder {
117: public :
118: XML256TableTranscoder2(
119: const XMLCh* const encodingName
120: , const unsigned int blockSize
121: , const XMLCh* const fromTable
122: , const XMLTransService::TransRec* const toTable
123: , const unsigned int toTableSize
124: ) : XML256TableTranscoder(encodingName, blockSize, fromTable, toTable, toTableSize) {}
125:
1.4 paf 126: private:
1.1 paf 127: XML256TableTranscoder2();
128: XML256TableTranscoder2(const XML256TableTranscoder2&);
129: void operator=(const XML256TableTranscoder2&);
130: };
1.8 paf 131: */
1.1 paf 132: #endif
133:
134: // methods
135:
136: extern "C" unsigned char pcre_default_tables[]; // pcre/chartables.c
1.7 paf 137: Charset::Charset(Pool& apool, const String& aname, const String *request_file_spec): Pooled(apool),
138: fname(aname) {
1.1 paf 139:
1.7 paf 140: const char *name_cstr=fname.cstr();
141:
142: if(request_file_spec) {
1.1 paf 143: fisUTF8=false;
1.7 paf 144: loadDefinition(*request_file_spec);
1.1 paf 145: #ifdef XML
146: addEncoding(name_cstr);
147: #endif
148: } else {
149: fisUTF8=true;
1.4 paf 150: // grab default onces [for UTF-8 so to be able to make a-z =>A-Z
1.1 paf 151: memcpy(pcre_tables, pcre_default_tables, sizeof(pcre_tables));
152: }
153:
154: #ifdef XML
155: initTranscoder(&aname, name_cstr);
156: #endif
157: }
158:
159: Charset::~Charset() {
160: #ifdef XML
1.9 ! paf 161: // not deleting transcoder, that's not our business
1.1 paf 162: #endif
163: }
164:
1.7 paf 165: void Charset::loadDefinition(const String& request_file_spec) {
1.1 paf 166: // pcre_tables
167: // lowcase, flipcase, bits digit+word+whitespace, masks
168:
169: // must not move this inside of prepare_case_tables
170: // don't know the size there
171: memset(pcre_tables, 0, sizeof(pcre_tables));
172: prepare_case_tables(pcre_tables);
1.4 paf 173: cstr2ctypes(pcre_tables,(const unsigned char *)"*+?{^.$|()[", ctype_meta);
1.1 paf 174:
175: // charset
176: memset(fromTable, 0, sizeof(fromTable));
1.5 paf 177: toTable=(Charset_TransRec *)calloc(sizeof(Charset_TransRec)*MAX_CHARSET_UNI_CODES);
1.1 paf 178: toTableSize=0;
179: // strangly vital
180: toTable[toTableSize].intCh=0;
181: toTable[toTableSize].extCh=(XMLByte)0;
182: toTableSize++;
183:
184: // loading text
1.7 paf 185: char *data=file_read_text(pool(), request_file_spec);
1.1 paf 186:
187: // ignore header
188: getrow(&data);
189:
190: // parse cells
191: char *row;
192: while(row=getrow(&data)) {
193: // remove empty&comment lines
194: if(!*row || *row=='#')
195: continue;
196:
197: // char white-space digit hex-digit letter word lowercase unicode1 unicode2
198: unsigned int c=0;
199: char *cell;
200: for(int column=0; cell=lsplit(&row, '\t'); column++) {
201: switch(column) {
202: case 0: c=to_wchar_code(cell); break;
203: // pcre_tables
204: case 1: element2ctypes(c, to_bool(cell), pcre_tables, ctype_space, cbit_space); break;
205: case 2: element2ctypes(c, to_bool(cell), pcre_tables, ctype_digit, cbit_digit); break;
206: case 3: element2ctypes(c, to_bool(cell), pcre_tables, ctype_xdigit); break;
207: case 4: element2ctypes(c, to_bool(cell), pcre_tables, ctype_letter); break;
208: case 5: element2ctypes(c, to_bool(cell), pcre_tables, ctype_word, cbit_word); break;
209: case 6: element2case(c, to_wchar_code(cell), pcre_tables); break;
210: case 7:
211: case 8:
212: // charset
213: if(toTableSize>MAX_CHARSET_UNI_CODES)
214: throw Exception(0, 0,
1.7 paf 215: &request_file_spec,
1.1 paf 216: "charset must contain not more then %d unicode values", MAX_CHARSET_UNI_CODES);
217:
218: XMLCh unicode=(XMLCh)to_wchar_code(cell);
219: if(!unicode && column==7/*unicode1 column*/)
220: unicode=(XMLCh)c;
221: if(unicode) {
222: if(!fromTable[c])
223: fromTable[c]=unicode;
224: toTable[toTableSize].intCh=unicode;
225: toTable[toTableSize].extCh=(XMLByte)c;
226: toTableSize++;
227: }
228: break;
229: }
230: }
231: };
232:
233: // sort by the Unicode code point
234: sort_ToTable();
235: }
236:
237: #ifdef XML
238: void Charset::addEncoding(const char *name_cstr) {
1.8 paf 239: /*
1.1 paf 240: // addEncoding
241: XalanDOMString sencoding(name_cstr);
242: const XMLCh* const auto_encoding_cstr=sencoding.c_str();
243: int size=sizeof(XMLCh)*(sencoding.size()+1);
244: XMLCh* pool_encoding_cstr=(XMLCh*)malloc(size);
245: memcpy(pool_encoding_cstr, auto_encoding_cstr, size);
246: XMLString::upperCase(pool_encoding_cstr);
247:
248: XMLPlatformUtils::fgTransService->addEncoding(
249: pool_encoding_cstr,
250: new ENameMapFor2<XML256TableTranscoder2>(
251: pool_encoding_cstr
252: , fromTable
253: , toTable
254: , toTableSize
255: ));
1.8 paf 256: */
1.1 paf 257: }
258:
259: void Charset::initTranscoder(const String *source, const char *name_cstr) {
1.8 paf 260: transcoder=xmlFindCharEncodingHandler(name_cstr);
1.1 paf 261: if(!transcoder)
262: throw Exception(0, 0,
263: source,
264: "unsupported encoding");
265: }
266: #endif
267:
268: static int sort_cmp_Trans_rec_intCh(const void *a, const void *b) {
269: return
270: static_cast<const Charset_TransRec *>(a)->intCh-
271: static_cast<const Charset_TransRec *>(b)->intCh;
272: }
273:
274: void Charset::sort_ToTable() {
275: _qsort(toTable, toTableSize, sizeof(*toTable),
276: sort_cmp_Trans_rec_intCh);
277: //FILE *f=fopen("c:\\temp\\a", "wb");
278: //fwrite(toTable, toTableSize, sizeof(*toTable), f);
279: //fclose(f);
280: }
281:
282: XMLByte Charset::xlatOneTo(const XMLCh toXlat) const {
283: unsigned int lowOfs = 0;
284: unsigned int hiOfs = toTableSize - 1;
285: XMLByte curByte = 0;
286: do {
287: // Calc the mid point of the low and high offset.
1.4 paf 288: const unsigned int midOfs =((hiOfs - lowOfs) / 2)+lowOfs;
1.1 paf 289:
290: // If our test char is greater than the mid point char, then
291: // we move up to the upper half. Else we move to the lower
292: // half. If its equal, then its our guy.
1.4 paf 293: if(toXlat>toTable[midOfs].intCh)
1.1 paf 294: lowOfs = midOfs;
1.4 paf 295: else if(toXlat<toTable[midOfs].intCh)
1.1 paf 296: hiOfs = midOfs;
297: else
298: return toTable[midOfs].extCh;
1.4 paf 299: } while(lowOfs+1<hiOfs);
1.1 paf 300:
301: return '?';
302: }
303:
304: void Charset::transcode(Pool& pool,
305: const Charset& source_charset, const void *source_body, size_t source_content_length,
306: const Charset& dest_charset, const void *& dest_body, size_t& dest_content_length
307: ) {
1.4 paf 308: if(!source_content_length) {
309: dest_body=0;
310: dest_content_length=0;
311: return;
312: }
313:
1.1 paf 314: switch((source_charset.isUTF8()?0x10:0x00)|(dest_charset.isUTF8()?0x01:0x00)) {
315: default: // 0x00
316: source_charset.transcodeToCharset(pool, dest_charset,
317: source_body, source_content_length,
318: dest_body, dest_content_length);
319: break;
320: case 0x01:
321: source_charset.transcodeToUTF8(pool,
322: source_body, source_content_length,
323: dest_body, dest_content_length);
324: break;
325: case 0x10:
326: dest_charset.transcodeFromUTF8(pool,
327: source_body, source_content_length,
328: dest_body, dest_content_length);
329: break;
330: case 0x11:
331: dest_body=source_body;
332: dest_content_length=source_content_length;
333: break;
334: }
335: }
336:
337: // ---------------------------------------------------------------------------
338: // Local static data
339: //
340: // gUTFBytes
341: // A list of counts of trailing bytes for each initial byte in the input.
342: //
343: // gUTFOffsets
344: // A list of values to offset each result char type, according to how
345: // many source bytes when into making it.
346: //
347: // gFirstByteMark
348: // A list of values to mask onto the first byte of an encoded sequence,
349: // indexed by the number of bytes used to create the sequence.
350: // ---------------------------------------------------------------------------
351: static const XMLByte gUTFBytes[0x100] = {
352: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
353: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
354: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
355: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
356: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
357: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
358: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
359: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
360: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
361: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
362: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
363: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
364: , 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
365: , 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
366: , 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
367: , 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
368: };
369:
370: static const uint gUTFOffsets[6] = {
371: 0, 0x3080, 0xE2080, 0x3C82080, 0xFA082080, 0x82082080
372: };
373:
374: static const XMLByte gFirstByteMark[7] = {
375: 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
376: };
377:
378: /// @todo not so memory-hungry with prescan
379: void Charset::transcodeToUTF8(Pool& pool,
380: const void *source_body, size_t source_content_length,
381: const void *& adest_body, size_t& adest_content_length) const {
382:
383: size_t dest_content_length=0;
384: XMLByte *dest_body=(XMLByte*)pool.malloc(source_content_length*6/*so that surly enough*/);
385:
386: const XMLByte* srcPtr=(const XMLByte*)source_body;
387: const XMLByte* srcEnd=(const XMLByte*)source_body+source_content_length;
388: XMLByte* outPtr=dest_body;
389:
1.4 paf 390: while(srcPtr<srcEnd) {
1.1 paf 391: uint curVal = fromTable[*srcPtr];
392: if(!curVal) {
393: // use the replacement character
1.4 paf 394: *outPtr++= '?';
395: srcPtr++;
1.1 paf 396: continue;
397: }
398:
399: // Figure out how many bytes we need
400: unsigned int encodedBytes;
1.4 paf 401: if(curVal<0x80)
1.1 paf 402: encodedBytes = 1;
1.4 paf 403: else if(curVal<0x800)
1.1 paf 404: encodedBytes = 2;
1.4 paf 405: else if(curVal<0x10000)
1.1 paf 406: encodedBytes = 3;
1.4 paf 407: else if(curVal<0x200000)
1.1 paf 408: encodedBytes = 4;
1.4 paf 409: else if(curVal<0x4000000)
1.1 paf 410: encodedBytes = 5;
1.4 paf 411: else if(curVal<= 0x7FFFFFFF)
1.1 paf 412: encodedBytes = 6;
413: else {
414: // use the replacement character
1.4 paf 415: *outPtr++= '?';
416: srcPtr++;
1.1 paf 417: continue;
418: }
419:
420: // If we cannot fully get this char into the output buffer,
421: // never
422:
423: // We can do it, so update the source index
424: srcPtr++;
425:
426: // And spit out the bytes. We spit them out in reverse order
427: // here, so bump up the output pointer and work down as we go.
1.4 paf 428: outPtr+= encodedBytes;
1.1 paf 429: switch(encodedBytes) {
1.4 paf 430: case 6: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
431: curVal>>= 6;
432: case 5: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
433: curVal>>= 6;
434: case 4: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
435: curVal>>= 6;
436: case 3: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
437: curVal>>= 6;
438: case 2: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
439: curVal>>= 6;
440: case 1: *--outPtr = XMLByte(curVal | gFirstByteMark[encodedBytes]);
1.1 paf 441: }
442:
443: // Add the encoded bytes back in again to indicate we've eaten them
1.4 paf 444: outPtr+= encodedBytes;
1.1 paf 445: }
446:
447: // return
448: adest_body=dest_body;
449: adest_content_length=outPtr-dest_body;
450: }
451: void Charset::transcodeFromUTF8(Pool& pool,
452: const void *source_body, size_t source_content_length,
453: const void *& adest_body, size_t& adest_content_length) const {
454: size_t dest_content_length=0;
455: XMLByte *dest_body=(XMLByte*)pool.malloc(source_content_length/*surly enough*/);
456:
457: const XMLByte* srcPtr=(const XMLByte*)source_body;
458: const XMLByte* srcEnd=(const XMLByte*)source_body+source_content_length;
459: XMLByte* outPtr=dest_body;
460:
461: // We now loop until we either run out of input data
1.4 paf 462: while(srcPtr<srcEnd) {
1.1 paf 463: // Get the next leading byte out
464: const XMLByte firstByte = *srcPtr;
465:
1.4 paf 466: // Special-case ASCII, which is a leading byte value of<= 127
467: if(firstByte<= 127) {
468: *outPtr++= firstByte;
1.1 paf 469: srcPtr++;
470: continue;
471: }
472:
473: // See how many trailing src bytes this sequence is going to require
474: const unsigned int trailingBytes = gUTFBytes[firstByte];
475:
476: // If there are not enough source bytes to do this one, then we
1.4 paf 477: // are done. Note that we done>= here because we are implicitly
1.1 paf 478: // counting the 1 byte we get no matter what.
1.4 paf 479: if(srcPtr+trailingBytes>= srcEnd)
1.1 paf 480: break;
481:
482: // Looks ok, so lets build up the value
483: uint tmpVal=0;
484: switch(trailingBytes) {
485: case 5: tmpVal+=*srcPtr++; tmpVal<<=6;
486: case 4: tmpVal+=*srcPtr++; tmpVal<<=6;
487: case 3: tmpVal+=*srcPtr++; tmpVal<<=6;
488: case 2: tmpVal+=*srcPtr++; tmpVal<<=6;
489: case 1: tmpVal+=*srcPtr++; tmpVal<<=6;
490: case 0: tmpVal+=*srcPtr++;
491: break;
492:
493: default:
494: throw Exception(0, 0,
495: 0,
1.4 paf 496: "transcodeFromUTF8 error: wrong trailingBytes value(%d)", trailingBytes);
1.1 paf 497: }
498: tmpVal-=gUTFOffsets[trailingBytes];
499:
500: // If it will fit into a single char, then put it in. Otherwise
501: // fail [*encode it as a surrogate pair. If its not valid, use the
502: // replacement char.*]
1.4 paf 503: if(!(tmpVal & 0xFFFF0000))
504: *outPtr++= xlatOneTo(tmpVal);
1.1 paf 505: else
506: throw Exception(0, 0,
507: 0,
1.4 paf 508: "transcodeFromUTF8 error: too big tmpVal(0x%08X)", tmpVal);
1.1 paf 509: }
510:
511: // return
512: adest_body=dest_body;
513: adest_content_length=outPtr-dest_body;
514: }
515:
516: /// transcode using both charsets
517: void Charset::transcodeToCharset(Pool& pool,
518: const Charset& dest_charset,
519: const void *source_body, size_t source_content_length,
1.6 paf 520: const void *& adest_body, size_t& adest_content_length) const {
1.3 paf 521: if(&dest_charset==this) {
1.6 paf 522: adest_body=source_body;
523: adest_content_length=source_content_length;
524: } else {
525: size_t dest_content_length=source_content_length;
526: unsigned char *dest_body=(unsigned char *)pool.malloc(dest_content_length);
527:
528: const XMLByte* srcPtr=(const XMLByte*)source_body;
529: const XMLByte* srcEnd=(const XMLByte*)source_body+source_content_length;
530:
531: for(XMLByte* outPtr=dest_body; srcPtr<srcEnd; srcPtr++) {
532: XMLCh curVal = fromTable[*srcPtr];
533: if(curVal)
534: *outPtr++=dest_charset.xlatOneTo(curVal);
535: else {
536: // use the replacement character
537: *outPtr++= '?';
538: }
539: }
1.1 paf 540:
1.6 paf 541: adest_body=dest_body;
542: adest_content_length=dest_content_length;
543: }
1.1 paf 544: }
545:
546: #ifdef XML
1.8 paf 547: const char *Charset::transcode_cstr(GdomeDOMString *s) {
548: if(!transcoder)
549: throw Exception(0, 0,
550: 0,
551: "transcode_cstr no transcoder");
552:
553: int inlen=gdome_str_length(s);
554: int outlen=inlen+1; // max
555: char *out=(char *)malloc(outlen*sizeof(char));
556:
557: int size=transcoder->output(
558: (unsigned char*)out, &outlen,
559: (const unsigned char*)s->str, &inlen);
560: if(size<0)
561: throw Exception(0, 0,
562: 0,
563: "transcode_cstr failed (%d)", size);
564:
565: out[size]=0;
566: return out;
1.1 paf 567: }
1.8 paf 568: String& Charset::transcode(GdomeDOMString *s) {
1.1 paf 569: return *NEW String(pool(), transcode_cstr(s));
570: }
571:
1.8 paf 572: /// @test less memory using -maybe- xmlParserInputBufferCreateMem
573: GdomeDOMString *Charset::transcode_buf(const char *buf, size_t buf_size) {
574: if(!transcoder)
575: throw Exception(0, 0,
576: 0,
577: "transcode_buf no transcoder");
578:
579: int outlen=buf_size*6/*max*/+1;
580: unsigned char *out=(unsigned char*)malloc(outlen*sizeof(unsigned char));
581: int size=transcoder->input(
582: out, &outlen,
583: (const unsigned char *)buf, (int *)&buf_size);
584: if(size<0)
585: throw Exception(0, 0,
586: 0,
587: "transcode_buf failed (%d)", size);
588:
589: out[size]=0;
590: return gdome_str_mkref_own((gchar*)out);
1.1 paf 591: }
1.8 paf 592: GdomeDOMString *Charset::transcode(const String& s) {
1.1 paf 593: const char *cstr=s.cstr(String::UL_UNSPECIFIED);
594:
595: return transcode_buf(cstr, strlen(cstr));
596: }
597: #endif
E-mail: