Annotation of parser3/src/main/pa_charset.C, revision 1.60
1.1 paf 1: /** @file
2: Parser: Charset connection implementation.
3:
1.52 paf 4: Copyright(c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com)
1.4 paf 5: Author: Alexander Petrosyan<paf@design.ru>(http://paf.design.ru)
1.27 paf 6: */
1.1 paf 7:
1.60 ! misha 8: static const char * const IDENT_CHARSET_C="$Date: 2008-06-05 13:23:22 $";
1.1 paf 9:
10: #include "pa_charset.h"
1.35 paf 11: #include "pa_charsets.h"
1.1 paf 12:
13: #ifdef XML
1.8 paf 14: #include "libxml/encoding.h"
1.1 paf 15: #endif
16:
1.46 paf 17: //#define PA_PATCHED_LIBXML_BACKWARD
1.60 ! misha 18: #define PRECALCULATE_DEST_LENGTH
1.46 paf 19:
1.38 paf 20: // globals
21:
22: Charset::UTF8CaseTable::Rec UTF8CaseToUpperRecords[]={
23: #include "utf8-to-upper.inc"
24: };
25: Charset::UTF8CaseTable UTF8CaseToUpper={
26: sizeof(UTF8CaseToUpperRecords)/sizeof(Charset::UTF8CaseTable::Rec),
27: UTF8CaseToUpperRecords};
28:
29: Charset::UTF8CaseTable::Rec UTF8CaseToLowerRecords[]={
30: #include "utf8-to-lower.inc"
31: };
32: Charset::UTF8CaseTable UTF8CaseToLower={
33: sizeof(UTF8CaseToLowerRecords)/sizeof(Charset::UTF8CaseTable::Rec),
34: UTF8CaseToLowerRecords};
35:
1.1 paf 36: // helpers
37:
38: inline void prepare_case_tables(unsigned char *tables) {
39: unsigned char *lcc_table=tables+lcc_offset;
40: unsigned char *fcc_table=tables+fcc_offset;
41: for(int i=0; i<0x100; i++)
1.53 paf 42: lcc_table[i]=fcc_table[i]=(unsigned char)i;
1.1 paf 43: }
44: inline void cstr2ctypes(unsigned char *tables, const unsigned char *cstr,
45: unsigned char bit) {
46: unsigned char *ctypes_table=tables+ctypes_offset;
47: ctypes_table[0]=bit;
48: for(; *cstr; cstr++) {
49: unsigned char c=*cstr;
50: ctypes_table[c]|=bit;
51: }
52: }
1.35 paf 53: inline unsigned int to_wchar_code(const char* cstr) {
1.1 paf 54: if(!cstr || !*cstr)
55: return 0;
56: if(cstr[1]==0)
1.4 paf 57: return(unsigned int)(unsigned char)cstr[0];
1.1 paf 58:
59: char *error_pos;
1.4 paf 60: return(unsigned int)strtol(cstr, &error_pos, 0);
1.1 paf 61: }
1.35 paf 62: inline bool to_bool(const char* cstr) {
1.1 paf 63: return cstr && *cstr!=0;
64: }
65: static void element2ctypes(unsigned char c, bool belongs,
66: unsigned char *tables, unsigned char bit, int group_offset=-1) {
67: if(!belongs)
68: return;
69:
70: unsigned char *ctypes_table=tables+ctypes_offset;
71:
72: ctypes_table[c]|=bit;
73: if(group_offset>=0)
1.4 paf 74: tables[cbits_offset+group_offset+c/8] |= 1<<(c%8);
1.1 paf 75: }
76: static void element2case(unsigned char from, unsigned char to,
77: unsigned char *tables) {
78: if(!to)
79: return;
80:
81: unsigned char *lcc_table=tables+lcc_offset;
82: unsigned char *fcc_table=tables+fcc_offset;
83: lcc_table[from]=to;
84: fcc_table[from]=to; fcc_table[to]=from;
85: }
86:
87: // methods
88:
89: extern "C" unsigned char pcre_default_tables[]; // pcre/chartables.c
1.37 paf 90: Charset::Charset(Request_charsets* charsets, const String::Body ANAME, const String* afile_spec):
1.35 paf 91: FNAME(ANAME),
92: FNAME_CSTR(ANAME.cstrm()) {
1.7 paf 93:
1.35 paf 94: if(afile_spec) {
1.1 paf 95: fisUTF8=false;
1.35 paf 96: load_definition(*charsets, *afile_spec);
1.1 paf 97: #ifdef XML
1.35 paf 98: addEncoding(FNAME_CSTR);
1.1 paf 99: #endif
100: } else {
101: fisUTF8=true;
1.4 paf 102: // grab default onces [for UTF-8 so to be able to make a-z =>A-Z
1.1 paf 103: memcpy(pcre_tables, pcre_default_tables, sizeof(pcre_tables));
104: }
105:
106: #ifdef XML
1.35 paf 107: initTranscoder(FNAME, FNAME_CSTR);
1.1 paf 108: #endif
109: }
110:
1.35 paf 111: void Charset::load_definition(Request_charsets& charsets, const String& afile_spec) {
1.1 paf 112: // pcre_tables
113: // lowcase, flipcase, bits digit+word+whitespace, masks
114:
115: // must not move this inside of prepare_case_tables
116: // don't know the size there
117: memset(pcre_tables, 0, sizeof(pcre_tables));
118: prepare_case_tables(pcre_tables);
1.4 paf 119: cstr2ctypes(pcre_tables,(const unsigned char *)"*+?{^.$|()[", ctype_meta);
1.1 paf 120:
121: // charset
1.35 paf 122: memset(&tables, 0, sizeof(tables));
1.1 paf 123:
124: // loading text
1.35 paf 125: char *data=file_read_text(charsets, afile_spec);
1.1 paf 126:
127: // ignore header
128: getrow(&data);
129:
130: // parse cells
131: char *row;
1.42 paf 132: while((row=getrow(&data))) {
1.1 paf 133: // remove empty&comment lines
134: if(!*row || *row=='#')
135: continue;
136:
137: // char white-space digit hex-digit letter word lowercase unicode1 unicode2
1.53 paf 138: unsigned char c=0;
1.1 paf 139: char *cell;
1.42 paf 140: for(int column=0; (cell=lsplit(&row, '\t')); column++) {
1.1 paf 141: switch(column) {
1.53 paf 142: case 0: c=(unsigned char)to_wchar_code(cell); break;
1.1 paf 143: // pcre_tables
144: case 1: element2ctypes(c, to_bool(cell), pcre_tables, ctype_space, cbit_space); break;
145: case 2: element2ctypes(c, to_bool(cell), pcre_tables, ctype_digit, cbit_digit); break;
146: case 3: element2ctypes(c, to_bool(cell), pcre_tables, ctype_xdigit); break;
147: case 4: element2ctypes(c, to_bool(cell), pcre_tables, ctype_letter); break;
148: case 5: element2ctypes(c, to_bool(cell), pcre_tables, ctype_word, cbit_word); break;
1.53 paf 149: case 6: element2case(c, (unsigned char)to_wchar_code(cell), pcre_tables); break;
1.1 paf 150: case 7:
151: case 8:
152: // charset
1.10 paf 153: if(tables.toTableSize>MAX_CHARSET_UNI_CODES)
1.56 misha 154: throw Exception(PARSER_RUNTIME,
1.35 paf 155: &afile_spec,
1.1 paf 156: "charset must contain not more then %d unicode values", MAX_CHARSET_UNI_CODES);
157:
158: XMLCh unicode=(XMLCh)to_wchar_code(cell);
159: if(!unicode && column==7/*unicode1 column*/)
160: unicode=(XMLCh)c;
161: if(unicode) {
1.10 paf 162: if(!tables.fromTable[c])
163: tables.fromTable[c]=unicode;
164: tables.toTable[tables.toTableSize].intCh=unicode;
165: tables.toTable[tables.toTableSize].extCh=(XMLByte)c;
166: tables.toTableSize++;
1.1 paf 167: }
168: break;
169: }
170: }
171: };
172:
173: // sort by the Unicode code point
174: sort_ToTable();
175: }
176:
177: static int sort_cmp_Trans_rec_intCh(const void *a, const void *b) {
178: return
1.38 paf 179: static_cast<const Charset::Tables::Rec *>(a)->intCh-
180: static_cast<const Charset::Tables::Rec *>(b)->intCh;
1.1 paf 181: }
182:
183: void Charset::sort_ToTable() {
1.10 paf 184: _qsort(tables.toTable, tables.toTableSize, sizeof(*tables.toTable),
1.1 paf 185: sort_cmp_Trans_rec_intCh);
186: //FILE *f=fopen("c:\\temp\\a", "wb");
1.10 paf 187: //fwrite(tables.toTable, tables.toTableSize, sizeof(*tables.toTable), f);
1.1 paf 188: //fclose(f);
189: }
190:
1.60 ! misha 191: // @todo: precache for spedup searching
1.10 paf 192: static XMLByte xlatOneTo(const XMLCh toXlat,
1.35 paf 193: const Charset::Tables& tables,
194: XMLByte not_found) {
1.39 paf 195: int lo = 0;
196: int hi = tables.toTableSize - 1;
197: while(lo<=hi) {
1.35 paf 198: // Calc the mid point of the low and high offset.
1.39 paf 199: const unsigned int i = (lo + hi) / 2;
200:
201: XMLCh cur=tables.toTable[i].intCh;
202: if(toXlat==cur)
203: return tables.toTable[i].extCh;
204: if(toXlat>cur)
205: lo = i+1;
1.1 paf 206: else
1.39 paf 207: hi = i-1;
208: }
1.35 paf 209:
210: return not_found;
1.1 paf 211: }
212:
1.35 paf 213: String::C Charset::transcode(const String::C src,
214: const Charset& source_charset,
215: const Charset& dest_charset) {
216: if(!src.length)
217: return String::C("", 0);
1.4 paf 218:
1.1 paf 219: switch((source_charset.isUTF8()?0x10:0x00)|(dest_charset.isUTF8()?0x01:0x00)) {
220: default: // 0x00
1.35 paf 221: return source_charset.transcodeToCharset(src, dest_charset);
1.1 paf 222: case 0x01:
1.35 paf 223: return source_charset.transcodeToUTF8(src);
1.1 paf 224: case 0x10:
1.35 paf 225: return dest_charset.transcodeFromUTF8(src);
1.1 paf 226: case 0x11:
1.35 paf 227: return src;
1.1 paf 228: }
229: }
230:
231: // ---------------------------------------------------------------------------
232: // Local static data
233: //
234: // gUTFBytes
235: // A list of counts of trailing bytes for each initial byte in the input.
236: //
237: // gUTFOffsets
238: // A list of values to offset each result char type, according to how
239: // many source bytes when into making it.
240: //
241: // gFirstByteMark
242: // A list of values to mask onto the first byte of an encoded sequence,
243: // indexed by the number of bytes used to create the sequence.
244: // ---------------------------------------------------------------------------
245: static const XMLByte gUTFBytes[0x100] = {
246: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
247: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
248: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
249: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
250: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
251: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
252: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
253: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
254: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
255: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
256: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
257: , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
258: , 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
259: , 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
260: , 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
261: , 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
262: };
263:
264: static const uint gUTFOffsets[6] = {
265: 0, 0x3080, 0xE2080, 0x3C82080, 0xFA082080, 0x82082080
266: };
267:
268: static const XMLByte gFirstByteMark[7] = {
269: 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
270: };
271:
1.35 paf 272: static int transcodeToUTF8(const XMLByte* srcData, size_t& srcLen,
273: XMLByte *toFill, size_t& toFillLen,
274: const Charset::Tables& tables) {
1.11 paf 275: const XMLByte* srcPtr=srcData;
276: const XMLByte* srcEnd=srcData+srcLen;
277: XMLByte* outPtr=toFill;
278: XMLByte* outEnd=toFill+toFillLen;
1.1 paf 279:
1.35 paf 280: while(srcPtr<srcEnd) {
281: uint curVal = tables.fromTable[*srcPtr];
1.1 paf 282: if(!curVal) {
1.35 paf 283: // use the replacement character
284: *outPtr++= '?';
285: srcPtr++;
286: continue;
287: }
1.1 paf 288:
1.35 paf 289: // Figure out how many bytes we need
290: unsigned int encodedBytes;
291: if(curVal<0x80)
292: encodedBytes = 1;
293: else if(curVal<0x800)
294: encodedBytes = 2;
295: else if(curVal<0x10000)
296: encodedBytes = 3;
297: else if(curVal<0x200000)
298: encodedBytes = 4;
299: else if(curVal<0x4000000)
300: encodedBytes = 5;
301: else if(curVal<= 0x7FFFFFFF)
302: encodedBytes = 6;
303: else {
304: // use the replacement character
305: *outPtr++= '?';
306: srcPtr++;
307: continue;
308: }
1.11 paf 309:
1.35 paf 310: // If we cannot fully get this char into the output buffer
311: if (outPtr + encodedBytes > outEnd)
312: break;
313:
314: // We can do it, so update the source index
315: srcPtr++;
316:
317: // And spit out the bytes. We spit them out in reverse order
318: // here, so bump up the output pointer and work down as we go.
319: outPtr+= encodedBytes;
320: switch(encodedBytes) {
1.60 ! misha 321: case 6: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
! 322: curVal>>= 6;
! 323: case 5: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
! 324: curVal>>= 6;
! 325: case 4: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
! 326: curVal>>= 6;
! 327: case 3: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
! 328: curVal>>= 6;
! 329: case 2: *--outPtr = XMLByte((curVal | 0x80UL) & 0xBFUL);
! 330: curVal>>= 6;
! 331: case 1: *--outPtr = XMLByte(curVal | gFirstByteMark[encodedBytes]);
1.35 paf 332: }
333:
334: // Add the encoded bytes back in again to indicate we've eaten them
335: outPtr+= encodedBytes;
336: }
337:
338: // Update the bytes eaten
339: srcLen = srcPtr - srcData;
340:
341: // Return the characters read
342: toFillLen = outPtr - toFill;
343:
1.29 paf 344: //return srcPtr==srcEnd?(int)toFillLen:-1;
345: /*
346: xmlCharEncodingInputFunc
347: Returns :
348: the number of byte written, or -1 by lack of space, or -2 if the transcoding failed. The value of inlen after return is the
349: number of octets consumed as the return value is positive, else unpredictiable. The value of outlen after return is the number
350: of ocetes consumed.
351: */
352: return 0;
1.1 paf 353: }
1.26 paf 354: /// @todo digital entites only when xml/html output [at output in html/xml mode, in html part of a letter]
1.35 paf 355: static int transcodeFromUTF8(const XMLByte* srcData, size_t& srcLen,
356: XMLByte* toFill, size_t& toFillLen,
357: const Charset::Tables& tables) {
1.11 paf 358: const XMLByte* srcPtr=srcData;
359: const XMLByte* srcEnd=srcData+srcLen;
360: XMLByte* outPtr=toFill;
361: XMLByte* outEnd=toFill+toFillLen;
1.1 paf 362:
1.35 paf 363: // We now loop until we either run out of input data, or room to store
364: while ((srcPtr < srcEnd) && (outPtr < outEnd)) {
365: // Get the next leading byte out
366: const XMLByte firstByte =* srcPtr;
367:
368: // Special-case ASCII, which is a leading byte value of<= 127
1.60 ! misha 369: if(firstByte<=127) {
1.35 paf 370: *outPtr++= firstByte;
371: srcPtr++;
372: continue;
373: }
374:
375: // See how many trailing src bytes this sequence is going to require
376: const unsigned int trailingBytes = gUTFBytes[firstByte];
377:
378: // If there are not enough source bytes to do this one, then we
379: // are done. Note that we done>= here because we are implicitly
380: // counting the 1 byte we get no matter what.
381: if(srcPtr+trailingBytes>= srcEnd)
382: break;
383:
384: // Looks ok, so lets build up the value
385: uint tmpVal=0;
386: switch(trailingBytes) {
387: case 5: tmpVal+=*srcPtr++; tmpVal<<=6;
388: case 4: tmpVal+=*srcPtr++; tmpVal<<=6;
389: case 3: tmpVal+=*srcPtr++; tmpVal<<=6;
390: case 2: tmpVal+=*srcPtr++; tmpVal<<=6;
391: case 1: tmpVal+=*srcPtr++; tmpVal<<=6;
392: case 0: tmpVal+=*srcPtr++;
393: break;
394:
395: default:
396: throw Exception(0,
397: 0,
1.49 paf 398: "transcodeFromUTF8 error: wrong trailingBytes value(%d)", trailingBytes); // never
1.35 paf 399: }
400: tmpVal-=gUTFOffsets[trailingBytes];
401:
402: // If it will fit into a single char, then put it in. Otherwise
403: // fail [*encode it as a surrogate pair. If its not valid, use the
404: // replacement char.*]
405: if(!(tmpVal & 0xFFFF0000)) {
1.25 paf 406: if(XMLByte xlat=xlatOneTo(tmpVal, tables, 0))
407: *outPtr++=xlat;
1.49 paf 408: else {
1.50 paf 409: outPtr+=sprintf((char *)outPtr, "&#%u;", tmpVal); // &#decimal;
1.49 paf 410: }
411: } else {
412: const XMLByte* recoverPtr=srcPtr-trailingBytes-1;
413: for(uint i=0; i<=trailingBytes; i++)
414: outPtr+=sprintf((char*)outPtr, "%%%02X", *recoverPtr++);
415: }
1.1 paf 416: }
1.35 paf 417:
418: // Update the bytes eaten
419: srcLen = srcPtr - srcData;
420:
421: // Return the characters read
422: toFillLen = outPtr - toFill;
1.11 paf 423:
1.29 paf 424: //return srcPtr==srcEnd?(int)toFillLen:-1;
425: /*
426: xmlCharEncodingOutputFunc
427: Returns :
428: the number of byte written, or -1 by lack of space, or -2 if the transcoding failed. The value of inlen after return is the
429: number of octets consumed as the return value is positive, else unpredictiable. The value of outlen after return is the number
430: of ocetes consumed.
431: */
432: return 0;
1.10 paf 433: }
434:
1.60 ! misha 435: static bool is_escaped(char c){
! 436: return
! 437: !(c<=127
! 438: && (
! 439: ((c>='0') && (c<='9'))
! 440: || ((c>='A') && (c<='Z'))
! 441: || ((c>='a') && (c<='z'))
! 442: || strchr("*@-_+./", c)!=0
! 443: ));
! 444: }
! 445:
! 446: // read one utf8 character, return number of bytes needed for store it
! 447: static unsigned int readChar(const XMLByte*& srcPtr, const XMLByte*& srcEnd, XMLByte& firstByte, XMLCh& UTF8Char){
! 448: if(!srcPtr || !*srcPtr || srcPtr>=srcEnd)
! 449: return 0;
! 450:
! 451: firstByte=*srcPtr;
! 452:
! 453: if(firstByte<=127){
! 454: UTF8Char=firstByte;
! 455: srcPtr++;
! 456: return 1;
! 457: }
! 458:
! 459: unsigned int trailingBytes=gUTFBytes[firstByte];
! 460:
! 461: if(srcPtr+trailingBytes>=srcEnd){
! 462: return 0; // not enough bytes in source string for reading
! 463: }
! 464:
! 465: uint tmpVal=0;
! 466: switch(trailingBytes){
! 467: case 5: tmpVal+=*srcPtr++; tmpVal<<=6;
! 468: case 4: tmpVal+=*srcPtr++; tmpVal<<=6;
! 469: case 3: tmpVal+=*srcPtr++; tmpVal<<=6;
! 470: case 2: tmpVal+=*srcPtr++; tmpVal<<=6;
! 471: case 1: tmpVal+=*srcPtr++; tmpVal<<=6;
! 472: case 0: tmpVal+=*srcPtr++;
! 473: }
! 474:
! 475: tmpVal-=gUTFOffsets[trailingBytes];
! 476: UTF8Char=tmpVal;
! 477:
! 478: return trailingBytes+1;
! 479: }
! 480:
! 481: // read char, return number of bytes needed for store it as UTF8
! 482: static unsigned int readChar(const XMLByte*& srcPtr, const XMLByte*& srcEnd, XMLByte& firstByte, XMLCh& UTF8Char, const Charset::Tables& tables){
! 483: if(!srcPtr || !*srcPtr || srcPtr>=srcEnd)
! 484: return 0;
! 485:
! 486: firstByte=*srcPtr++;
! 487: UTF8Char=tables.fromTable[firstByte];
! 488:
! 489: if(UTF8Char<0x80)
! 490: return 1;
! 491: else if(UTF8Char<0x800)
! 492: return 2;
! 493: else if(UTF8Char<0x10000)
! 494: return 3;
! 495: else if(UTF8Char<0x200000)
! 496: return 4;
! 497: else if(UTF8Char<0x4000000)
! 498: return 5;
! 499: else if(UTF8Char<= 0x7FFFFFFF)
! 500: return 6;
! 501:
! 502: // will use the replacement character '?'
! 503: firstByte=0;
! 504: return 1;
! 505: }
! 506:
! 507: static int escape(const XMLByte* srcData, size_t& srcLen,
! 508: XMLByte* toFill, size_t& toFillLen) {
! 509: const XMLByte* srcPtr=srcData;
! 510: const XMLByte* srcEnd=srcData+srcLen;
! 511: XMLByte* outPtr=toFill;
! 512: XMLByte* outEnd=toFill+toFillLen;
! 513: XMLByte firstByte;
! 514: XMLCh UTF8Char;
! 515: uint charSize;
! 516:
! 517: // loop until we either run out of input data, or room to store
! 518: while((outPtr < outEnd) && (charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char))){
! 519: if(charSize==1){
! 520: if(is_escaped(firstByte)) // %XX
! 521: outPtr+=sprintf((char*)outPtr, "%%%02X", firstByte);
! 522: else
! 523: *outPtr++=firstByte;
! 524: } else
! 525: outPtr+=sprintf((char*)outPtr, "%%u%04X", UTF8Char); // %uXXXX
! 526: }
! 527:
! 528: // Update the bytes eaten
! 529: srcLen=srcPtr-srcData;
! 530:
! 531: // Return the characters read
! 532: toFillLen=outPtr-toFill;
! 533:
! 534: return 0;
! 535: }
! 536:
! 537: static int escape(const XMLByte* srcData, size_t& srcLen,
! 538: XMLByte *toFill, size_t& toFillLen,
! 539: const Charset::Tables& tables) {
! 540: const XMLByte* srcPtr=srcData;
! 541: const XMLByte* srcEnd=srcData+srcLen;
! 542: XMLByte* outPtr=toFill;
! 543: //XMLByte* outEnd=toFill+toFillLen;
! 544: XMLByte firstByte;
! 545: XMLCh UTF8Char;
! 546: uint charSize;
! 547:
! 548: while(charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char, tables)){
! 549: if(charSize==1){
! 550: if(firstByte){
! 551: if(is_escaped(firstByte)) // %XX
! 552: outPtr+=sprintf((char*)outPtr, "%%%02X", firstByte);
! 553: else
! 554: *outPtr++=firstByte;
! 555: } else // add replacement char '?'
! 556: *outPtr++='?';
! 557: } else
! 558: outPtr+=sprintf((char*)outPtr, "%%u%04X", UTF8Char); // %uXXXX
! 559: }
! 560:
! 561: // Update the bytes eaten
! 562: srcLen = srcPtr - srcData;
! 563:
! 564: // Return the characters read
! 565: toFillLen = outPtr - toFill;
! 566:
! 567: return 0;
! 568: }
! 569:
! 570:
! 571: String::C Charset::escape(const String::C src, const Charset& source_charset){
! 572: size_t src_length=src.length;
! 573: if(!src_length)
! 574: return String::C("", 0);
! 575:
! 576: #ifdef PRECALCULATE_DEST_LENGTH
! 577: size_t dest_length=0;
! 578: const XMLByte* srcPtr=(XMLByte*)src.str;
! 579: const XMLByte* srcEnd=srcPtr+src_length;
! 580: XMLByte firstByte;
! 581: XMLCh UTF8Char;
! 582:
! 583: if(source_charset.isUTF8()){
! 584: while(uint charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char)){
! 585: if(charSize==1)
! 586: dest_length+=!is_escaped(firstByte)?1:3/*%XX*/;
! 587: else
! 588: dest_length+=6; // '%uXXXX'
! 589: }
! 590: } else {
! 591: while(uint charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char, source_charset.tables)){
! 592: if(charSize==1)
! 593: dest_length+=(!firstByte/*replacement char '?'*/ || !is_escaped(firstByte))?1:3/*'%XX'*/;
! 594: else
! 595: dest_length+=6; // '%uXXXX'
! 596: }
! 597: }
! 598: #else
! 599: size_t dest_length=src_length*6; // enough for %uXXXX but too memory-hungry
! 600: #endif
! 601:
! 602: //throw Exception(0,0,"%u",dest_length);
! 603:
! 604: #ifndef NDEBUG
! 605: size_t saved_dest_length=dest_length;
! 606: #endif
! 607: XMLByte *dest_body=new(PointerFreeGC) XMLByte[dest_length+1/*for terminator*/];
! 608:
! 609: int status;
! 610: if(source_charset.isUTF8()){
! 611: status=::escape((XMLByte *)src.str, src_length, dest_body, dest_length);
! 612: } else {
! 613: status=::escape((XMLByte *)src.str, src_length, dest_body, dest_length, source_charset.tables);
! 614: }
! 615:
! 616: if(status<0)
! 617: throw Exception(0,
! 618: 0,
! 619: "Charset::escapeString buffer overflow");
! 620:
! 621: assert(dest_length<=saved_dest_length);
! 622: dest_body[dest_length]=0; // terminator
! 623: return String::C((char*)dest_body, dest_length);
! 624: }
! 625:
! 626:
1.35 paf 627: const String::C Charset::transcodeToUTF8(const String::C src) const {
628: size_t src_length=src.length;
1.60 ! misha 629:
! 630: #ifdef PRECALCULATE_DEST_LENGTH
! 631: size_t dest_length=0;
! 632: const XMLByte* srcPtr=(XMLByte*)src.str;
! 633: const XMLByte* srcEnd=srcPtr+src_length;
! 634: XMLByte firstByte;
! 635: XMLCh UTF8Char;
! 636: while(uint charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char, tables))
! 637: dest_length+=charSize;
! 638: #else
! 639: size_t dest_length=src_length*6; // so that surly enough (max utf8 seq len=6) but too memory-hyngry
! 640: #endif
! 641:
! 642: //throw Exception(0,0,"%u",dest_length);
! 643:
1.35 paf 644: #ifndef NDEBUG
645: size_t saved_dest_length=dest_length;
646: #endif
647: XMLByte *dest_body=new(PointerFreeGC) XMLByte[dest_length+1/*for terminator*/];
1.11 paf 648:
649: if(::transcodeToUTF8(
1.35 paf 650: (XMLByte *)src.str, src_length,
651: dest_body, dest_length,
1.11 paf 652: tables)<0)
1.43 paf 653: throw Exception(0,
1.10 paf 654: 0,
1.11 paf 655: "Charset::transcodeToUTF8 buffer overflow");
1.10 paf 656:
1.60 ! misha 657: assert(dest_length<=saved_dest_length);
! 658: dest_body[dest_length]=0; // terminator
1.35 paf 659: return String::C((char*)dest_body, dest_length);
1.10 paf 660: }
1.38 paf 661:
662: static XMLCh change_case_UTF8(const XMLCh src, const Charset::UTF8CaseTable& table) {
1.39 paf 663: int lo = 0;
664: int hi = table.size - 1;
665: while(lo<=hi) {
1.38 paf 666: // Calc the mid point of the low and high offset.
1.39 paf 667: const unsigned int i = (lo + hi) / 2;
668:
669: XMLCh cur=table.records[i].from;
670: if(src==cur)
671: return table.records[i].to;
672: if(src>cur)
673: lo = i+1;
1.38 paf 674: else
1.39 paf 675: hi = i-1;
676: }
677:
678: // not found
1.38 paf 679: return src;
680: }
681:
1.58 misha 682: static void store_UTF8(XMLCh src, XMLByte*& outPtr){
1.38 paf 683: if(!src) {
684: // use the replacement character
685: *outPtr++= '?';
686: return;
687: }
688:
689: // Figure out how many bytes we need
690: unsigned int encodedBytes;
691: if(src<0x80)
692: encodedBytes = 1;
693: else if(src<0x800)
694: encodedBytes = 2;
695: else if(src<0x10000)
696: encodedBytes = 3;
697: else if(src<0x200000)
698: encodedBytes = 4;
699: else if(src<0x4000000)
700: encodedBytes = 5;
701: else if(src<= 0x7FFFFFFF)
702: encodedBytes = 6;
703: else {
704: // use the replacement character
705: *outPtr++= '?';
706: return;
707: }
708:
709: // And spit out the bytes. We spit them out in reverse order
710: // here, so bump up the output pointer and work down as we go.
711: outPtr+= encodedBytes;
712: switch(encodedBytes) {
713: case 6: *--outPtr = XMLByte((src | 0x80UL) & 0xBFUL);
714: src>>= 6;
715: case 5: *--outPtr = XMLByte((src | 0x80UL) & 0xBFUL);
716: src>>= 6;
717: case 4: *--outPtr = XMLByte((src | 0x80UL) & 0xBFUL);
718: src>>= 6;
719: case 3: *--outPtr = XMLByte((src | 0x80UL) & 0xBFUL);
720: src>>= 6;
721: case 2: *--outPtr = XMLByte((src | 0x80UL) & 0xBFUL);
722: src>>= 6;
723: case 1: *--outPtr = XMLByte(src | gFirstByteMark[encodedBytes]);
724: }
725:
726: // Add the encoded bytes back in again to indicate we've eaten them
727: outPtr+= encodedBytes;
728: }
729:
730: static void change_case_UTF8(XMLCh src, XMLByte*& outPtr,
731: const Charset::UTF8CaseTable& table) {
732: store_UTF8(change_case_UTF8(src, table), outPtr);
733: };
1.44 paf 734: void change_case_UTF8(const XMLByte* srcData, size_t srcLen,
735: XMLByte* toFill, size_t toFillLen,
736: const Charset::UTF8CaseTable& table) {
1.38 paf 737: const XMLByte* srcPtr=srcData;
1.44 paf 738: const XMLByte* srcEnd=srcData+srcLen;
1.38 paf 739: XMLByte* outPtr=toFill;
1.44 paf 740: XMLByte* outEnd=toFill+toFillLen;
741:
742: // We now loop until we either run out of input data, or room to store
743: while ((srcPtr < srcEnd) && (outPtr < outEnd)) {
744: // Get the next leading byte out
745: const XMLByte firstByte =* srcPtr;
1.38 paf 746:
1.60 ! misha 747: if(firstByte<=127) {
1.38 paf 748: change_case_UTF8(firstByte, outPtr, table);
749: srcPtr++;
750: continue;
751: }
752:
753: // See how many trailing src bytes this sequence is going to require
754: const unsigned int trailingBytes = gUTFBytes[firstByte];
755:
756: // Looks ok, so lets build up the value
757: uint tmpVal=0;
758: switch(trailingBytes) {
759: case 5: tmpVal+=*srcPtr++; tmpVal<<=6;
760: case 4: tmpVal+=*srcPtr++; tmpVal<<=6;
761: case 3: tmpVal+=*srcPtr++; tmpVal<<=6;
762: case 2: tmpVal+=*srcPtr++; tmpVal<<=6;
763: case 1: tmpVal+=*srcPtr++; tmpVal<<=6;
764: case 0: tmpVal+=*srcPtr++;
765: break;
766:
767: default:
768: throw Exception(0,
769: 0,
770: "change_case_UTF8 error: wrong trailingBytes value(%d)", trailingBytes);
771: }
772: tmpVal-=gUTFOffsets[trailingBytes];
773:
774: // If it will fit into a single char, then put it in. Otherwise
775: // fail [*encode it as a surrogate pair. If its not valid, use the
776: // replacement char.*]
777: if(!(tmpVal & 0xFFFF0000))
778: change_case_UTF8(tmpVal, outPtr, table);
779: else
780: throw Exception(0,
781: 0,
782: "change_case_UTF8 error: too big tmpVal(0x%08X)", tmpVal);
783: }
784:
785: if(srcPtr!=outPtr)
786: throw Exception(0,
787: 0,
788: "change_case_UTF8 error: end pointers do not match");
789: }
790:
1.60 ! misha 791: static size_t getDecNumLength(XMLCh UTF8Char){
! 792: return
! 793: (UTF8Char < 100)
! 794: ?2
! 795: :(UTF8Char < 1000)
! 796: ?3
! 797: :(UTF8Char < 10000)
! 798: ?4
! 799: :5;
! 800: }
1.38 paf 801:
1.35 paf 802: const String::C Charset::transcodeFromUTF8(const String::C src) const {
803: size_t src_length=src.length;
1.60 ! misha 804:
! 805: #ifdef PRECALCULATE_DEST_LENGTH
! 806: size_t dest_length=0;
! 807: const XMLByte* srcPtr=(XMLByte*)src.str;
! 808: const XMLByte* srcEnd=srcPtr+src_length;
! 809: XMLByte firstByte;
! 810: XMLCh UTF8Char;
! 811: while(uint charSize=readChar(srcPtr, srcEnd, firstByte, UTF8Char)){
! 812: if(charSize==1)
! 813: dest_length++;
! 814: else
! 815: dest_length+=(UTF8Char & 0xFFFF0000)
! 816: ?charSize*3 // '%XX' for each byte
! 817: :(xlatOneTo(UTF8Char, tables, 0)!=0)
! 818: ?1 // can convert it to single char
! 819: :getDecNumLength(UTF8Char)+3; // &#XX; - &#XXXXX;
! 820: }
! 821: #else
! 822: // so that surly enough, "&#XXX;" has max ratio (huh? 8 bytes needed for '&#XXXXX;')
! 823: size_t dest_length=src_length*6;
! 824: #endif
! 825:
! 826: //throw Exception(0,0,"%u",dest_length);
! 827:
1.35 paf 828: #ifndef NDEBUG
829: size_t saved_dest_length=dest_length;
830: #endif
831: XMLByte *dest_body=new(PointerFreeGC) XMLByte[dest_length+1/*for terminator*/];
1.11 paf 832:
833: if(::transcodeFromUTF8(
1.35 paf 834: (XMLByte *)src.str, src_length,
835: dest_body, dest_length,
1.11 paf 836: tables)<0)
1.43 paf 837: throw Exception(0,
1.10 paf 838: 0,
1.35 paf 839: "Charset::transcodeFromUTF8 buffer overflow");
1.10 paf 840:
1.60 ! misha 841: assert(dest_length<=saved_dest_length);
! 842: dest_body[dest_length]=0; // terminator
1.35 paf 843: return String::C((char*)dest_body, dest_length);
1.1 paf 844: }
845:
846: /// transcode using both charsets
1.35 paf 847: const String::C Charset::transcodeToCharset(const String::C src,
848: const Charset& dest_charset) const {
849: if(&dest_charset==this)
850: return src;
851: else {
852: size_t dest_length=src.length;
853: XMLByte* dest_body=new(PointerFreeGC) XMLByte[dest_length+1/*for terminator*/];
854:
855: XMLByte* output=dest_body;
856: const XMLByte* input=(XMLByte *)src.str;
857: while(XMLCh c=*input++) {
858: XMLCh curVal = tables.fromTable[c];
859: *output++=curVal?
860: xlatOneTo(curVal, dest_charset.tables, '?') // OK
861: :'?'; // use the replacement character
1.6 paf 862: }
1.1 paf 863:
1.35 paf 864: dest_body[dest_length]=0; // terminator
865: return String::C((char*)dest_body, dest_length);
1.6 paf 866: }
1.1 paf 867: }
868:
1.58 misha 869: void Charset::store_Char(XMLByte*& outPtr, XMLCh src, XMLByte not_found){
1.59 misha 870: if(isUTF8())
1.58 misha 871: store_UTF8(src, outPtr);
1.59 misha 872: else if(char ch=xlatOneTo(src, tables, not_found))
1.58 misha 873: *outPtr++=ch;
1.57 misha 874: }
875:
1.1 paf 876: #ifdef XML
1.10 paf 877:
1.35 paf 878: static const Charset::Tables* tables[MAX_CHARSETS];
879:
1.46 paf 880: #ifdef PA_PATCHED_LIBXML_BACKWARD
881:
882: #define declareXml256ioFuncs(i) \
883: static int xml256CharEncodingInputFunc##i( \
884: unsigned char *out, int *outlen, \
885: const unsigned char *in, int *inlen, void*) { \
886: return transcodeToUTF8( \
887: in, *(size_t*)inlen, \
888: out, *(size_t*)outlen, \
889: *tables[i]); \
890: } \
891: static int xml256CharEncodingOutputFunc##i( \
892: unsigned char *out, int *outlen, \
893: const unsigned char *in, int *inlen, void*) { \
894: return transcodeFromUTF8( \
895: in, *(size_t*)inlen, \
896: out, *(size_t*)outlen, \
897: *tables[i]); \
898: }
899:
900: #else
901:
1.35 paf 902: #define declareXml256ioFuncs(i) \
903: static int xml256CharEncodingInputFunc##i( \
904: unsigned char *out, int *outlen, \
905: const unsigned char *in, int *inlen) { \
906: return transcodeToUTF8( \
907: in, *(size_t*)inlen, \
908: out, *(size_t*)outlen, \
909: *tables[i]); \
910: } \
911: static int xml256CharEncodingOutputFunc##i( \
912: unsigned char *out, int *outlen, \
913: const unsigned char *in, int *inlen) { \
914: return transcodeFromUTF8( \
915: in, *(size_t*)inlen, \
916: out, *(size_t*)outlen, \
917: *tables[i]); \
918: }
919:
1.46 paf 920: #endif
921:
922:
1.35 paf 923: declareXml256ioFuncs(0) declareXml256ioFuncs(1)
924: declareXml256ioFuncs(2) declareXml256ioFuncs(3)
925: declareXml256ioFuncs(4) declareXml256ioFuncs(5)
926: declareXml256ioFuncs(6) declareXml256ioFuncs(7)
927: declareXml256ioFuncs(8) declareXml256ioFuncs(9)
928:
929: static xmlCharEncodingInputFunc inputFuncs[MAX_CHARSETS]={
930: xml256CharEncodingInputFunc0, xml256CharEncodingInputFunc1,
931: xml256CharEncodingInputFunc2, xml256CharEncodingInputFunc3,
932: xml256CharEncodingInputFunc4, xml256CharEncodingInputFunc5,
933: xml256CharEncodingInputFunc6, xml256CharEncodingInputFunc7,
934: xml256CharEncodingInputFunc8, xml256CharEncodingInputFunc9
935: };
936: static xmlCharEncodingOutputFunc outputFuncs[MAX_CHARSETS]={
937: xml256CharEncodingOutputFunc0, xml256CharEncodingOutputFunc1,
938: xml256CharEncodingOutputFunc2, xml256CharEncodingOutputFunc3,
939: xml256CharEncodingOutputFunc4, xml256CharEncodingOutputFunc5,
940: xml256CharEncodingOutputFunc6, xml256CharEncodingOutputFunc7,
941: xml256CharEncodingOutputFunc8, xml256CharEncodingOutputFunc9
942: };
943: static size_t handlers_count=0;
1.10 paf 944:
945: void Charset::addEncoding(char *name_cstr) {
1.35 paf 946: if(handlers_count==MAX_CHARSETS)
947: throw Exception(0,
948: 0,
949: "already allocated %d handlers, no space for new encoding '%s'",
950: MAX_CHARSETS, name_cstr);
951:
1.45 paf 952: xmlCharEncodingHandler* handler=new(UseGC) xmlCharEncodingHandler;
1.35 paf 953: {
954: handler->name=name_cstr;
955: handler->input=inputFuncs[handlers_count];
956: handler->output=outputFuncs[handlers_count];
957: ::tables[handlers_count]=&tables;
958: handlers_count++;
959: }
1.10 paf 960:
961: xmlRegisterCharEncodingHandler(handler);
1.35 paf 962:
1.10 paf 963: }
964:
1.37 paf 965: void Charset::initTranscoder(const String::Body NAME, const char* name_cstr) {
1.15 paf 966: ftranscoder=xmlFindCharEncodingHandler(name_cstr);
1.35 paf 967: transcoder(NAME); // check right way
1.15 paf 968: }
969:
1.37 paf 970: xmlCharEncodingHandler& Charset::transcoder(const String::Body NAME) {
1.15 paf 971: if(!ftranscoder)
1.56 misha 972: throw Exception(PARSER_RUNTIME,
1.35 paf 973: new String(NAME, String::L_TAINTED),
1.10 paf 974: "unsupported encoding");
1.35 paf 975: return *ftranscoder;
1.10 paf 976: }
977:
1.54 paf 978: String::C Charset::transcode_cstr(const xmlChar* s) {
1.13 paf 979: if(!s)
1.35 paf 980: return String::C("", 0);
1.8 paf 981:
1.35 paf 982: int inlen=strlen((const char*)s);
1.51 paf 983: int outlen=inlen*6/*strlen("ÿ")*/; // max
1.35 paf 984: #ifndef NDEBUG
985: int saved_outlen=outlen;
986: #endif
987: char *out=new(PointerFreeGC) char[outlen+1];
1.8 paf 988:
1.30 paf 989: int error;
1.35 paf 990: if(xmlCharEncodingOutputFunc output=transcoder(FNAME).output) {
1.30 paf 991: error=output(
1.17 paf 992: (unsigned char*)out, &outlen,
1.46 paf 993: (const unsigned char*)s, &inlen
994: #ifdef PA_PATCHED_LIBXML_BACKWARD
995: ,0
996: #endif
997: );
1.30 paf 998: } else {
999: memcpy(out, s, outlen=inlen);
1000: error=0;
1001: }
1002: if(error<0)
1.23 paf 1003: throw Exception(0,
1.8 paf 1004: 0,
1.30 paf 1005: "transcode_cstr failed (%d)", error);
1.8 paf 1006:
1.35 paf 1007: assert(outlen<=saved_outlen); out[outlen]=0;
1008: return String::C(out, outlen);
1.14 paf 1009: }
1.54 paf 1010: const String& Charset::transcode(const xmlChar* s) {
1.35 paf 1011: String::C cstr=transcode_cstr(s);
1012: return *new String(cstr.str, cstr.length, true);
1.1 paf 1013: }
1014:
1.8 paf 1015: /// @test less memory using -maybe- xmlParserInputBufferCreateMem
1.35 paf 1016: xmlChar* Charset::transcode_buf2xchar(const char* buf, size_t buf_size) {
1017: xmlChar* out;
1.30 paf 1018: int outlen;
1019: int error;
1.35 paf 1020: #ifndef NDEBUG
1021: int saved_outlen;
1022: #endif
1023: if(xmlCharEncodingInputFunc input=transcoder(FNAME).input) {
1.51 paf 1024: outlen=buf_size*6/*max UTF8 bytes per char*/;
1.35 paf 1025: #ifndef NDEBUG
1026: saved_outlen=outlen;
1027: #endif
1.47 paf 1028: out=(xmlChar*)xmlMalloc(outlen+1);
1.30 paf 1029: error=input(
1.17 paf 1030: out, &outlen,
1.46 paf 1031: (const unsigned char*)buf, (int*)&buf_size
1032: #ifdef PA_PATCHED_LIBXML_BACKWARD
1033: ,0
1034: #endif
1035: );
1.30 paf 1036: } else {
1037: outlen=buf_size;
1.35 paf 1038: #ifndef NDEBUG
1039: saved_outlen=outlen;
1040: #endif
1041: out=(xmlChar*)xmlMalloc(outlen+1);
1.30 paf 1042: memcpy(out, buf, outlen);
1043: error=0;
1044: }
1.17 paf 1045:
1.30 paf 1046: if(error<0)
1.23 paf 1047: throw Exception(0,
1.8 paf 1048: 0,
1.30 paf 1049: "transcode_buf failed (%d)", error);
1.8 paf 1050:
1.35 paf 1051: assert(outlen<=saved_outlen); out[outlen]=0;
1052: return out;
1.24 paf 1053: }
1.54 paf 1054: xmlChar* Charset::transcode(const String& s) {
1.35 paf 1055: const char* cstr=s.cstr(String::L_UNSPECIFIED);
1.1 paf 1056:
1.54 paf 1057: return transcode_buf2xchar(cstr, strlen(cstr));
1.1 paf 1058: }
1.54 paf 1059: xmlChar* Charset::transcode(const String::Body s) {
1.35 paf 1060: const char* cstr=s.cstr();
1061:
1.54 paf 1062: return transcode_buf2xchar(cstr, s.length());
1.35 paf 1063: }
1.36 paf 1064: #endif
1.34 paf 1065:
1.37 paf 1066: String::Body Charset::transcode(const String::Body src,
1.34 paf 1067: const Charset& source_transcoder,
1.35 paf 1068: const Charset& dest_transcoder) {
1.34 paf 1069:
1.35 paf 1070: const char *src_ptr=src.cstr();
1.34 paf 1071: size_t src_size=strlen(src_ptr);
1072:
1.35 paf 1073: String::C dest=Charset::transcode(String::C(src_ptr, src_size),
1074: source_transcoder,
1075: dest_transcoder);
1.34 paf 1076:
1.37 paf 1077: return String::Body(dest.str, dest.length);
1.35 paf 1078: }
1079:
1080: String& Charset::transcode(const String& src,
1081: const Charset& source_transcoder,
1082: const Charset& dest_transcoder) {
1083: if(!src.length())
1084: return *new String("", 0, false);
1.34 paf 1085:
1.37 paf 1086: return *new String(transcode((String::Body)src, source_transcoder, dest_transcoder), String::L_CLEAN);
1.34 paf 1087: }
1088:
1.35 paf 1089: void Charset::transcode(ArrayString& src,
1.34 paf 1090: const Charset& source_transcoder,
1.35 paf 1091: const Charset& dest_transcoder) {
1092: for(size_t i=0; i<src.count(); i++)
1093: src.put(i, &transcode(*src[i], source_transcoder, dest_transcoder));
1.34 paf 1094: }
1095:
1096: #ifndef DOXYGEN
1097: struct Transcode_pair_info {
1098: const Charset* source_transcoder;
1099: const Charset* dest_transcoder;
1100: };
1101: #endif
1.40 paf 1102: static void transcode_pair(const String::Body /*akey*/,
1.37 paf 1103: String::Body& avalue,
1.35 paf 1104: Transcode_pair_info* info) {
1105: avalue=Charset::transcode(avalue,
1106: *info->source_transcoder,
1107: *info->dest_transcoder);
1.34 paf 1108: }
1.35 paf 1109: void Charset::transcode(HashStringString& src,
1.34 paf 1110: const Charset& source_transcoder,
1.35 paf 1111: const Charset& dest_transcoder) {
1112: Transcode_pair_info info={&source_transcoder, &dest_transcoder};
1.55 paf 1113: src.for_each_ref<Transcode_pair_info*>(transcode_pair, &info);
1.34 paf 1114: }
E-mail: