xref: /trunk/main/basic/source/comp/scanner.cxx (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_basic.hxx"
30 
31 #include "sbcomp.hxx"
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
35 #if defined UNX
36 #include <stdlib.h>
37 #else
38 #include <math.h>   // atof()
39 #endif
40 #include <rtl/math.hxx>
41 #include <vcl/svapp.hxx>
42 #include <unotools/charclass.hxx>
43 
44 #include <runtime.hxx>
45 
46 SbiScanner::SbiScanner( const ::rtl::OUString& rBuf, StarBASIC* p ) : aBuf( rBuf )
47 {
48     pBasic   = p;
49     pLine    = NULL;
50     nVal     = 0;
51     eScanType = SbxVARIANT;
52     nErrors  = 0;
53     nBufPos  = 0;
54     nCurCol1 = 0;
55     nSavedCol1 = 0;
56     nColLock = 0;
57     nLine    = 0;
58     nCol1    = 0;
59     nCol2    = 0;
60     nCol     = 0;
61     bError   =
62     bAbort   =
63     bSpaces  =
64     bNumber  =
65     bSymbol  =
66     bUsedForHilite =
67     bCompatible =
68     bVBASupportOn =
69     bPrevLineExtentsComment = sal_False;
70     bHash    =
71     bErrors  = sal_True;
72 }
73 
74 SbiScanner::~SbiScanner()
75 {}
76 
77 void SbiScanner::LockColumn()
78 {
79     if( !nColLock++ )
80         nSavedCol1 = nCol1;
81 }
82 
83 void SbiScanner::UnlockColumn()
84 {
85     if( nColLock )
86         nColLock--;
87 }
88 
89 void SbiScanner::GenError( SbError code )
90 {
91     if( GetSbData()->bBlockCompilerError )
92     {
93         bAbort = sal_True;
94         return;
95     }
96     if( !bError && bErrors )
97     {
98         sal_Bool bRes = sal_True;
99         // Nur einen Fehler pro Statement reporten
100         bError = sal_True;
101         if( pBasic )
102         {
103             // Falls EXPECTED oder UNEXPECTED kommen sollte, bezieht es sich
104             // immer auf das letzte Token, also die Col1 uebernehmen
105             sal_uInt16 nc = nColLock ? nSavedCol1 : nCol1;
106             switch( code )
107             {
108                 case SbERR_EXPECTED:
109                 case SbERR_UNEXPECTED:
110                 case SbERR_SYMBOL_EXPECTED:
111                 case SbERR_LABEL_EXPECTED:
112                     nc = nCol1;
113                     if( nc > nCol2 ) nCol2 = nc;
114                     break;
115             }
116             bRes = pBasic->CError( code, aError, nLine, nc, nCol2 );
117         }
118         bAbort |= !bRes |
119              ( code == SbERR_NO_MEMORY || code == SbERR_PROG_TOO_LARGE );
120     }
121     if( bErrors )
122         nErrors++;
123 }
124 
125 // Falls sofort ein Doppelpunkt folgt, wird sal_True zurueckgeliefert.
126 // Wird von SbiTokenizer::MayBeLabel() verwendet, um einen Label zu erkennen
127 
128 sal_Bool SbiScanner::DoesColonFollow()
129 {
130     if( pLine && *pLine == ':' )
131     {
132         pLine++; nCol++; return sal_True;
133     }
134     else return sal_False;
135 }
136 
137 // Testen auf ein legales Suffix
138 
139 static SbxDataType GetSuffixType( sal_Unicode c )
140 {
141     static String aSuffixesStr = String::CreateFromAscii( "%&!#@ $" );
142     if( c )
143     {
144         sal_uInt32 n = aSuffixesStr.Search( c );
145         if( STRING_NOTFOUND != n && c != ' ' )
146             return SbxDataType( (sal_uInt16) n + SbxINTEGER );
147     }
148     return SbxVARIANT;
149 }
150 
151 // Einlesen des naechsten Symbols in die Variablen aSym, nVal und eType
152 // Returnwert ist sal_False bei EOF oder Fehlern
153 #define BUF_SIZE 80
154 
155 namespace {
156 
157 /** Returns true, if the passed character is a white space character. */
158 inline bool lclIsWhitespace( sal_Unicode cChar )
159 {
160     return (cChar == ' ') || (cChar == '\t') || (cChar == '\f');
161 }
162 
163 } // namespace
164 
165 sal_Bool SbiScanner::NextSym()
166 {
167     // Fuer den EOLN-Fall merken
168     sal_uInt16 nOldLine = nLine;
169     sal_uInt16 nOldCol1 = nCol1;
170     sal_uInt16 nOldCol2 = nCol2;
171     sal_Unicode buf[ BUF_SIZE ], *p = buf;
172     bHash = sal_False;
173 
174     eScanType = SbxVARIANT;
175     aSym.Erase();
176     bSymbol =
177     bNumber = bSpaces = sal_False;
178 
179     // Zeile einlesen?
180     if( !pLine )
181     {
182         sal_Int32 n = nBufPos;
183         sal_Int32 nLen = aBuf.getLength();
184         if( nBufPos >= nLen )
185             return sal_False;
186         const sal_Unicode* p2 = aBuf.getStr();
187         p2 += n;
188         while( ( n < nLen ) && ( *p2 != '\n' ) && ( *p2 != '\r' ) )
189             p2++, n++;
190         // #163944# ignore trailing whitespace
191         sal_Int32 nCopyEndPos = n;
192         while( (nBufPos < nCopyEndPos) && lclIsWhitespace( aBuf[ nCopyEndPos - 1 ] ) )
193             --nCopyEndPos;
194         aLine = aBuf.copy( nBufPos, nCopyEndPos - nBufPos );
195         if( n < nLen )
196         {
197             if( *p2 == '\r' && *( p2+1 ) == '\n' )
198                 n += 2;
199             else
200                 n++;
201         }
202         nBufPos = n;
203         pLine = aLine.getStr();
204         nOldLine = ++nLine;
205         nCol = nCol1 = nCol2 = nOldCol1 = nOldCol2 = 0;
206         nColLock = 0;
207     }
208 
209     // Leerstellen weg:
210     while( lclIsWhitespace( *pLine ) )
211         pLine++, nCol++, bSpaces = sal_True;
212 
213     nCol1 = nCol;
214 
215     // nur Leerzeile?
216     if( !*pLine )
217         goto eoln;
218 
219     if( bPrevLineExtentsComment )
220         goto PrevLineCommentLbl;
221 
222     if( *pLine == '#' )
223     {
224         pLine++;
225         nCol++;
226         bHash = sal_True;
227     }
228 
229     // Symbol? Dann Zeichen kopieren.
230     if( BasicSimpleCharClass::isAlpha( *pLine, bCompatible ) || *pLine == '_' )
231     {
232         // Wenn nach '_' nichts kommt, ist es ein Zeilenabschluss!
233         if( *pLine == '_' && !*(pLine+1) )
234         {   pLine++;
235             goto eoln;  }
236         bSymbol = sal_True;
237         short n = nCol;
238         for ( ; (BasicSimpleCharClass::isAlphaNumeric( *pLine, bCompatible ) || ( *pLine == '_' ) ); pLine++ )
239             nCol++;
240         aSym = aLine.copy( n, nCol - n );
241 
242         // Special handling for "go to"
243         if( bCompatible && *pLine && aSym.EqualsIgnoreCaseAscii( "go" ) )
244         {
245             const sal_Unicode* pTestLine = pLine;
246             short nTestCol = nCol;
247             while( lclIsWhitespace( *pTestLine ) )
248             {
249                 pTestLine++;
250                 nTestCol++;
251             }
252 
253             if( *pTestLine && *(pTestLine + 1) )
254             {
255                 String aTestSym = aLine.copy( nTestCol, 2 );
256                 if( aTestSym.EqualsIgnoreCaseAscii( "to" ) )
257                 {
258                     aSym = String::CreateFromAscii( "goto" );
259                     pLine = pTestLine + 2;
260                     nCol = nTestCol + 2;
261                 }
262             }
263         }
264 
265         // Abschliessendes '_' durch Space ersetzen, wenn Zeilenende folgt
266         // (sonst falsche Zeilenfortsetzung)
267         if( !bUsedForHilite && !*pLine && *(pLine-1) == '_' )
268         {
269             aSym.GetBufferAccess();     // #109693 force copy if necessary
270             *((sal_Unicode*)(pLine-1)) = ' ';       // cast wegen const
271         }
272         // Typkennung?
273         // Das Ausrufezeichen bitte nicht testen, wenn
274         // danach noch ein Symbol anschliesst
275         else if( *pLine != '!' || !BasicSimpleCharClass::isAlpha( pLine[ 1 ], bCompatible ) )
276         {
277             SbxDataType t = GetSuffixType( *pLine );
278             if( t != SbxVARIANT )
279             {
280                 eScanType = t;
281                 pLine++;
282                 nCol++;
283             }
284         }
285     }
286 
287     // Zahl? Dann einlesen und konvertieren.
288     else if( BasicSimpleCharClass::isDigit( *pLine & 0xFF )
289         || ( *pLine == '.' && BasicSimpleCharClass::isDigit( *(pLine+1) & 0xFF ) ) )
290     {
291         short exp = 0;
292         short comma = 0;
293         short ndig = 0;
294         short ncdig = 0;
295         eScanType = SbxDOUBLE;
296         sal_Bool bBufOverflow = sal_False;
297         while( strchr( "0123456789.DEde", *pLine ) && *pLine )
298         {
299             // AB 4.1.1996: Buffer voll? -> leer weiter scannen
300             if( (p-buf) == (BUF_SIZE-1) )
301             {
302                 bBufOverflow = sal_True;
303                 pLine++, nCol++;
304                 continue;
305             }
306             // Komma oder Exponent?
307             if( *pLine == '.' )
308             {
309                 if( ++comma > 1 )
310                 {
311                     pLine++; nCol++; continue;
312                 }
313                 else *p++ = *pLine++, nCol++;
314             }
315             else if( strchr( "DdEe", *pLine ) )
316             {
317                 if (++exp > 1)
318                 {
319                     pLine++; nCol++; continue;
320                 }
321 //              if( toupper( *pLine ) == 'D' )
322 //                  eScanType = SbxDOUBLE;
323                 *p++ = 'E'; pLine++; nCol++;
324                 // Vorzeichen hinter Exponent?
325                 if( *pLine == '+' )
326                     pLine++, nCol++;
327                 else
328                 if( *pLine == '-' )
329                     *p++ = *pLine++, nCol++;
330             }
331             else
332             {
333                 *p++ = *pLine++, nCol++;
334                 if( comma && !exp ) ncdig++;
335             }
336             if (!exp) ndig++;
337         }
338         *p = 0;
339         aSym = p; bNumber = sal_True;
340         // Komma, Exponent mehrfach vorhanden?
341         if( comma > 1 || exp > 1 )
342         {   aError = '.';
343             GenError( SbERR_BAD_CHAR_IN_NUMBER );   }
344 
345         // #57844 Lokalisierte Funktion benutzen
346         nVal = rtl_math_uStringToDouble( buf, buf+(p-buf), '.', ',', NULL, NULL );
347         // ALT: nVal = atof( buf );
348 
349         ndig = ndig - comma;
350         if( !comma && !exp )
351         {
352             if( nVal >= SbxMININT && nVal <= SbxMAXINT )
353                 eScanType = SbxINTEGER;
354             else
355             if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
356                 eScanType = SbxLONG;
357         }
358         if( bBufOverflow )
359             GenError( SbERR_MATH_OVERFLOW );
360         // zu viele Zahlen fuer SINGLE?
361 //      if (ndig > 15 || ncdig > 6)
362 //          eScanType = SbxDOUBLE;
363 //      else
364 //      if( nVal > SbxMAXSNG || nVal < SbxMINSNG )
365 //          eScanType = SbxDOUBLE;
366 
367         // Typkennung?
368         SbxDataType t = GetSuffixType( *pLine );
369         if( t != SbxVARIANT )
370         {
371             eScanType = t;
372             pLine++;
373             nCol++;
374         }
375     }
376 
377     // Hex/Oktalzahl? Einlesen und konvertieren:
378     else if( *pLine == '&' )
379     {
380         pLine++; nCol++;
381         sal_Unicode cmp1[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', 0 };
382         sal_Unicode cmp2[] = { '0', '1', '2', '3', '4', '5', '6', '7', 0 };
383         sal_Unicode *cmp = cmp1;
384         //char *cmp = "0123456789ABCDEF";
385         sal_Unicode base = 16;
386         sal_Unicode ndig = 8;
387         sal_Unicode xch  = *pLine++ & 0xFF; nCol++;
388         switch( toupper( xch ) )
389         {
390             case 'O':
391                 cmp = cmp2; base = 8; ndig = 11; break;
392                 //cmp = "01234567"; base = 8; ndig = 11; break;
393             case 'H':
394                 break;
395             default :
396                 // Wird als Operator angesehen
397                 pLine--; nCol--; nCol1 = nCol-1; aSym = '&'; return SYMBOL;
398         }
399         bNumber = sal_True;
400         long l = 0;
401         int i;
402         sal_Bool bBufOverflow = sal_False;
403         while( BasicSimpleCharClass::isAlphaNumeric( *pLine & 0xFF, bCompatible ) )
404         {
405             sal_Unicode ch = sal::static_int_cast< sal_Unicode >(
406                 toupper( *pLine & 0xFF ) );
407             pLine++; nCol++;
408             // AB 4.1.1996: Buffer voll, leer weiter scannen
409             if( (p-buf) == (BUF_SIZE-1) )
410                 bBufOverflow = sal_True;
411             else if( String( cmp ).Search( ch ) != STRING_NOTFOUND )
412             //else if( strchr( cmp, ch ) )
413                 *p++ = ch;
414             else
415             {
416                 aError = ch;
417                 GenError( SbERR_BAD_CHAR_IN_NUMBER );
418             }
419         }
420         *p = 0;
421         for( p = buf; *p; p++ )
422         {
423             i = (*p & 0xFF) - '0';
424             if( i > 9 ) i -= 7;
425             l = ( l * base ) + i;
426             if( !ndig-- )
427             {
428                 GenError( SbERR_MATH_OVERFLOW ); break;
429             }
430         }
431         if( *pLine == '&' ) pLine++, nCol++;
432         nVal = (double) l;
433         eScanType = ( l >= SbxMININT && l <= SbxMAXINT ) ? SbxINTEGER : SbxLONG;
434         if( bBufOverflow )
435             GenError( SbERR_MATH_OVERFLOW );
436     }
437 
438     // Strings:
439     else if( *pLine == '"' || *pLine == '[' )
440     {
441         sal_Unicode cSep = *pLine;
442         if( cSep == '[' )
443             bSymbol = sal_True, cSep = ']';
444         short n = nCol+1;
445         while( *pLine )
446         {
447             do pLine++, nCol++;
448             while( *pLine && ( *pLine != cSep ) );
449             if( *pLine == cSep )
450             {
451                 pLine++; nCol++;
452                 if( *pLine != cSep || cSep == ']' ) break;
453             } else aError = cSep, GenError( SbERR_EXPECTED );
454         }
455         // If VBA Interop then doen't eat the [] chars
456         if ( cSep == ']' && bVBASupportOn )
457             aSym = aLine.copy( n - 1, nCol - n  + 1);
458         else
459             aSym = aLine.copy( n, nCol - n - 1 );
460         // Doppelte Stringbegrenzer raus
461         String s( cSep );
462         s += cSep;
463         sal_uInt16 nIdx = 0;
464         do
465         {
466             nIdx = aSym.Search( s, nIdx );
467             if( nIdx == STRING_NOTFOUND )
468                 break;
469             aSym.Erase( nIdx, 1 );
470             nIdx++;
471         }
472         while( true );
473         if( cSep != ']' )
474             eScanType = ( cSep == '#' ) ? SbxDATE : SbxSTRING;
475     }
476     // ungueltige Zeichen:
477     else if( ( *pLine & 0xFF ) >= 0x7F )
478     {
479         GenError( SbERR_SYNTAX ); pLine++; nCol++;
480     }
481     // andere Gruppen:
482     else
483     {
484         short n = 1;
485         switch( *pLine++ )
486         {
487             case '<': if( *pLine == '>' || *pLine == '=' ) n = 2; break;
488             case '>': if( *pLine == '=' ) n = 2; break;
489             case ':': if( *pLine == '=' ) n = 2; break;
490         }
491         aSym = aLine.copy( nCol, n );
492         pLine += n-1; nCol = nCol + n;
493     }
494 
495     nCol2 = nCol-1;
496 
497 PrevLineCommentLbl:
498     // Kommentar?
499     if( bPrevLineExtentsComment || (eScanType != SbxSTRING &&
500         ( aSym.GetBuffer()[0] == '\'' || aSym.EqualsIgnoreCaseAscii( "REM" ) ) ) )
501     {
502         bPrevLineExtentsComment = sal_False;
503         aSym = String::CreateFromAscii( "REM" );
504         sal_uInt16 nLen = String( pLine ).Len();
505         if( bCompatible && pLine[ nLen - 1 ] == '_' && pLine[ nLen - 2 ] == ' ' )
506             bPrevLineExtentsComment = sal_True;
507         nCol2 = nCol2 + nLen;
508         pLine = NULL;
509     }
510     return sal_True;
511 
512     // Sonst Zeilen-Ende: aber bitte auf '_' testen, ob die
513     // Zeile nicht weitergeht!
514 eoln:
515     if( nCol && *--pLine == '_' )
516     {
517         pLine = NULL;
518         bool bRes = NextSym();
519         if( bVBASupportOn && aSym.GetBuffer()[0] == '.' )
520         {
521             // object _
522             //    .Method
523             // ^^^  <- spaces is legal in MSO VBA
524             OSL_TRACE("*** resetting bSpaces***");
525             bSpaces = sal_False;
526         }
527         return bRes;
528     }
529     else
530     {
531         pLine = NULL;
532         nLine = nOldLine;
533         nCol1 = nOldCol1;
534         nCol2 = nOldCol2;
535         aSym = '\n';
536         nColLock = 0;
537         return sal_True;
538     }
539 }
540 
541 LetterTable BasicSimpleCharClass::aLetterTable;
542 
543 LetterTable::LetterTable( void )
544 {
545     for( int i = 0 ; i < 256 ; ++i )
546         IsLetterTab[i] = false;
547 
548     IsLetterTab[0xC0] = true;   // � , CAPITAL LETTER A WITH GRAVE ACCENT
549     IsLetterTab[0xC1] = true;   // � , CAPITAL LETTER A WITH ACUTE ACCENT
550     IsLetterTab[0xC2] = true;   // � , CAPITAL LETTER A WITH CIRCUMFLEX ACCENT
551     IsLetterTab[0xC3] = true;   // � , CAPITAL LETTER A WITH TILDE
552     IsLetterTab[0xC4] = true;   // � , CAPITAL LETTER A WITH DIAERESIS
553     IsLetterTab[0xC5] = true;   // � , CAPITAL LETTER A WITH RING ABOVE
554     IsLetterTab[0xC6] = true;   // � , CAPITAL LIGATURE AE
555     IsLetterTab[0xC7] = true;   // � , CAPITAL LETTER C WITH CEDILLA
556     IsLetterTab[0xC8] = true;   // � , CAPITAL LETTER E WITH GRAVE ACCENT
557     IsLetterTab[0xC9] = true;   // � , CAPITAL LETTER E WITH ACUTE ACCENT
558     IsLetterTab[0xCA] = true;   // � , CAPITAL LETTER E WITH CIRCUMFLEX ACCENT
559     IsLetterTab[0xCB] = true;   // � , CAPITAL LETTER E WITH DIAERESIS
560     IsLetterTab[0xCC] = true;   // � , CAPITAL LETTER I WITH GRAVE ACCENT
561     IsLetterTab[0xCD] = true;   // � , CAPITAL LETTER I WITH ACUTE ACCENT
562     IsLetterTab[0xCE] = true;   // � , CAPITAL LETTER I WITH CIRCUMFLEX ACCENT
563     IsLetterTab[0xCF] = true;   // � , CAPITAL LETTER I WITH DIAERESIS
564     IsLetterTab[0xD0] = true;   // � , CAPITAL LETTER ETH
565     IsLetterTab[0xD1] = true;   // � , CAPITAL LETTER N WITH TILDE
566     IsLetterTab[0xD2] = true;   // � , CAPITAL LETTER O WITH GRAVE ACCENT
567     IsLetterTab[0xD3] = true;   // � , CAPITAL LETTER O WITH ACUTE ACCENT
568     IsLetterTab[0xD4] = true;   // � , CAPITAL LETTER O WITH CIRCUMFLEX ACCENT
569     IsLetterTab[0xD5] = true;   // � , CAPITAL LETTER O WITH TILDE
570     IsLetterTab[0xD6] = true;   // � , CAPITAL LETTER O WITH DIAERESIS
571     IsLetterTab[0xD8] = true;   // � , CAPITAL LETTER O WITH STROKE
572     IsLetterTab[0xD9] = true;   // � , CAPITAL LETTER U WITH GRAVE ACCENT
573     IsLetterTab[0xDA] = true;   // � , CAPITAL LETTER U WITH ACUTE ACCENT
574     IsLetterTab[0xDB] = true;   // � , CAPITAL LETTER U WITH CIRCUMFLEX ACCENT
575     IsLetterTab[0xDC] = true;   // � , CAPITAL LETTER U WITH DIAERESIS
576     IsLetterTab[0xDD] = true;   // � , CAPITAL LETTER Y WITH ACUTE ACCENT
577     IsLetterTab[0xDE] = true;   // � , CAPITAL LETTER THORN
578     IsLetterTab[0xDF] = true;   // � , SMALL LETTER SHARP S
579     IsLetterTab[0xE0] = true;   // � , SMALL LETTER A WITH GRAVE ACCENT
580     IsLetterTab[0xE1] = true;   // � , SMALL LETTER A WITH ACUTE ACCENT
581     IsLetterTab[0xE2] = true;   // � , SMALL LETTER A WITH CIRCUMFLEX ACCENT
582     IsLetterTab[0xE3] = true;   // � , SMALL LETTER A WITH TILDE
583     IsLetterTab[0xE4] = true;   // � , SMALL LETTER A WITH DIAERESIS
584     IsLetterTab[0xE5] = true;   // � , SMALL LETTER A WITH RING ABOVE
585     IsLetterTab[0xE6] = true;   // � , SMALL LIGATURE AE
586     IsLetterTab[0xE7] = true;   // � , SMALL LETTER C WITH CEDILLA
587     IsLetterTab[0xE8] = true;   // � , SMALL LETTER E WITH GRAVE ACCENT
588     IsLetterTab[0xE9] = true;   // � , SMALL LETTER E WITH ACUTE ACCENT
589     IsLetterTab[0xEA] = true;   // � , SMALL LETTER E WITH CIRCUMFLEX ACCENT
590     IsLetterTab[0xEB] = true;   // � , SMALL LETTER E WITH DIAERESIS
591     IsLetterTab[0xEC] = true;   // � , SMALL LETTER I WITH GRAVE ACCENT
592     IsLetterTab[0xED] = true;   // � , SMALL LETTER I WITH ACUTE ACCENT
593     IsLetterTab[0xEE] = true;   // � , SMALL LETTER I WITH CIRCUMFLEX ACCENT
594     IsLetterTab[0xEF] = true;   // � , SMALL LETTER I WITH DIAERESIS
595     IsLetterTab[0xF0] = true;   // � , SMALL LETTER ETH
596     IsLetterTab[0xF1] = true;   // � , SMALL LETTER N WITH TILDE
597     IsLetterTab[0xF2] = true;   // � , SMALL LETTER O WITH GRAVE ACCENT
598     IsLetterTab[0xF3] = true;   // � , SMALL LETTER O WITH ACUTE ACCENT
599     IsLetterTab[0xF4] = true;   // � , SMALL LETTER O WITH CIRCUMFLEX ACCENT
600     IsLetterTab[0xF5] = true;   // � , SMALL LETTER O WITH TILDE
601     IsLetterTab[0xF6] = true;   // � , SMALL LETTER O WITH DIAERESIS
602     IsLetterTab[0xF8] = true;   // � , SMALL LETTER O WITH OBLIQUE BAR
603     IsLetterTab[0xF9] = true;   // � , SMALL LETTER U WITH GRAVE ACCENT
604     IsLetterTab[0xFA] = true;   // � , SMALL LETTER U WITH ACUTE ACCENT
605     IsLetterTab[0xFB] = true;   // � , SMALL LETTER U WITH CIRCUMFLEX ACCENT
606     IsLetterTab[0xFC] = true;   // � , SMALL LETTER U WITH DIAERESIS
607     IsLetterTab[0xFD] = true;   // � , SMALL LETTER Y WITH ACUTE ACCENT
608     IsLetterTab[0xFE] = true;   // � , SMALL LETTER THORN
609     IsLetterTab[0xFF] = true;   // � , SMALL LETTER Y WITH DIAERESIS
610 }
611 
612 bool LetterTable::isLetterUnicode( sal_Unicode c )
613 {
614     static CharClass* pCharClass = NULL;
615     if( pCharClass == NULL )
616         pCharClass = new CharClass( Application::GetSettings().GetLocale() );
617     String aStr( c );
618     bool bRet = pCharClass->isLetter( aStr, 0 );
619     return bRet;
620 }
621