xref: /trunk/main/i18npool/source/transliteration/transliteration_body.cxx (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_i18npool.hxx"
26 
27 #include <rtl/ustrbuf.hxx>
28 #include <i18nutil/casefolding.hxx>
29 #include <i18nutil/unicode.hxx>
30 
31 #include <comphelper/processfactory.hxx>
32 #include <osl/diagnose.h>
33 
34 #include <string.h>
35 
36 #include "characterclassificationImpl.hxx"
37 #include "breakiteratorImpl.hxx"
38 
39 #define TRANSLITERATION_ALL
40 #include "transliteration_body.hxx"
41 
42 using namespace ::com::sun::star::uno;
43 using namespace ::com::sun::star::lang;
44 using namespace ::rtl;
45 
46 #define A2OU(x) OUString::createFromAscii(x)
47 
48 namespace com { namespace sun { namespace star { namespace i18n {
49 
50 
Transliteration_body()51 Transliteration_body::Transliteration_body()
52 {
53     nMappingType = 0;
54     transliterationName = "Transliteration_body";
55     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_body";
56 }
57 
getType()58 sal_Int16 SAL_CALL Transliteration_body::getType()
59 {
60     return TransliterationType::ONE_TO_ONE;
61 }
62 
equals(const OUString &,sal_Int32,sal_Int32,sal_Int32 &,const OUString &,sal_Int32,sal_Int32,sal_Int32 &)63 sal_Bool SAL_CALL Transliteration_body::equals(
64     const OUString& /*str1*/, sal_Int32 /*pos1*/, sal_Int32 /*nCount1*/, sal_Int32& /*nMatch1*/,
65     const OUString& /*str2*/, sal_Int32 /*pos2*/, sal_Int32 /*nCount2*/, sal_Int32& /*nMatch2*/)
66 {
67     throw RuntimeException();
68 }
69 
70 Sequence< OUString > SAL_CALL
transliterateRange(const OUString & str1,const OUString & str2)71 Transliteration_body::transliterateRange( const OUString& str1, const OUString& str2 )
72 {
73     Sequence< OUString > ostr(2);
74     ostr[0] = str1;
75     ostr[1] = str2;
76     return ostr;
77 }
78 
79 
lcl_getMappingTypeForToggleCase(sal_uInt8 nMappingType,sal_Unicode cChar)80 static sal_uInt8 lcl_getMappingTypeForToggleCase( sal_uInt8 nMappingType, sal_Unicode cChar )
81 {
82     sal_uInt8 nRes = nMappingType;
83 
84     // take care of TOGGLE_CASE transliteration:
85     // nMappingType should not be a combination of flags, thuse we decide now
86     // which one to use.
87     if (nMappingType == (MappingTypeLowerToUpper | MappingTypeUpperToLower))
88     {
89         const sal_Int16 nType = unicode::getUnicodeType( cChar );
90         if (nType & 0x02 /* lower case*/)
91             nRes = MappingTypeLowerToUpper;
92         else
93         {
94             // should also work properly for non-upper characters like white spacs, numbers, ...
95             nRes = MappingTypeUpperToLower;
96         }
97     }
98 
99     return nRes;
100 }
101 
102 
103 OUString SAL_CALL
transliterate(const OUString & inStr,sal_Int32 startPos,sal_Int32 nCount,Sequence<sal_Int32> & offset)104 Transliteration_body::transliterate(
105     const OUString& inStr, sal_Int32 startPos, sal_Int32 nCount,
106     Sequence< sal_Int32 >& offset)
107 {
108 #if 0
109 /* Performance optimization:
110  * The two realloc() consume 48% (32% grow, 16% shrink) runtime of this method!
111  * getValue() needs about 15%, so there is equal balance if we trade the second
112  * (shrinking) realloc() for a getValue(). But if the caller initializes the
113  * sequence to nCount elements there isn't any change in size necessary in most
114  * cases (one-to-one mapping) and we gain 33%.
115  *
116  * Of that constellation the getValue() method takes 20% upon each call, so 40%
117  * for both. By remembering the first calls' results we could gain some extra
118  * percentage again, but unfortunately getValue() may return a reference to a
119  * static buffer, so we can't store the pointer directly but would have to
120  * copy-construct an array, which doesn't give us any advantage.
121  *
122  * Much more is accomplished by working directly on the sequence buffer
123  * returned by getArray() instead of using operator[] for each and every
124  * access.
125  *
126  * And while we're at it: now that we know the size in advance we don't need to
127  * copy the buffer anymore, just create the real string buffer and let the
128  * return value take ownership.
129  *
130  * All together these changes result in the new implementation needing only 62%
131  * of the time of the old implementation (in other words: that one was 1.61
132  * times slower ...)
133  */
134 
135     // Allocate the max possible buffer. Try to use stack instead of heap which
136     // would have to be reallocated most times anyway.
137     const sal_Int32 nLocalBuf = 512 * NMAPPINGMAX;
138     sal_Unicode aLocalBuf[nLocalBuf], *out = aLocalBuf, *aHeapBuf = NULL;
139 
140     const sal_Unicode *in = inStr.getStr() + startPos;
141 
142     if (nCount > 512)
143         out = aHeapBuf =  (sal_Unicode*) malloc((nCount * NMAPPINGMAX) * sizeof(sal_Unicode));
144 
145         if (useOffset)
146             offset.realloc(nCount * NMAPPINGMAX);
147     sal_Int32 j = 0;
148     for (sal_Int32 i = 0; i < nCount; i++) {
149         Mapping &map = casefolding::getValue(in, i, nCount, aLocale, nMappingType);
150         for (sal_Int32 k = 0; k < map.nmap; k++) {
151                 if (useOffset)
152                     offset[j] = i + startPos;
153         out[j++] = map.map[k];
154         }
155     }
156         if (useOffset)
157             offset.realloc(j);
158 
159     OUString r(out, j);
160 
161     if (aHeapBuf)
162         free(aHeapBuf);
163 
164     return r;
165 #else
166     const sal_Unicode *in = inStr.getStr() + startPos;
167 
168     // Two different blocks to eliminate the if(useOffset) condition inside the
169     // inner k loop. Yes, on massive use even such small things do count.
170     if ( useOffset )
171     {
172         sal_Int32 nOffCount = 0, i;
173         for (i = 0; i < nCount; i++)
174         {
175             // take care of TOGGLE_CASE transliteration:
176             sal_uInt8 nTmpMappingType = nMappingType;
177             if (nMappingType == (MappingTypeLowerToUpper | MappingTypeUpperToLower))
178                 nTmpMappingType = lcl_getMappingTypeForToggleCase( nMappingType, in[i] );
179 
180             const Mapping &map = casefolding::getValue( in, i, nCount, aLocale, nTmpMappingType );
181             nOffCount += map.nmap;
182         }
183         rtl_uString* pStr = x_rtl_uString_new_WithLength( nOffCount );  // our x_rtl_ustring.h
184         sal_Unicode* out = pStr->buffer;
185 
186         if ( nOffCount != offset.getLength() )
187             offset.realloc( nOffCount );
188 
189         sal_Int32 j = 0;
190         sal_Int32 * pArr = offset.getArray();
191         for (i = 0; i < nCount; i++)
192         {
193             // take care of TOGGLE_CASE transliteration:
194             sal_uInt8 nTmpMappingType = nMappingType;
195             if (nMappingType == (MappingTypeLowerToUpper | MappingTypeUpperToLower))
196                 nTmpMappingType = lcl_getMappingTypeForToggleCase( nMappingType, in[i] );
197 
198             const Mapping &map = casefolding::getValue( in, i, nCount, aLocale, nTmpMappingType );
199             for (sal_Int32 k = 0; k < map.nmap; k++)
200             {
201                 pArr[j] = i + startPos;
202                 out[j++] = map.map[k];
203             }
204         }
205         out[j] = 0;
206 
207         return OUString( pStr, SAL_NO_ACQUIRE ); // take over ownership of <pStr>
208     }
209     else
210     {
211         // In the simple case of no offset sequence used we can eliminate the
212         // first getValue() loop. We could also assume that most calls result
213         // in identical string lengths, thus using a preallocated
214         // OUStringBuffer could be an easy way to assemble the return string
215         // without too much hassle. However, for single characters the
216         // OUStringBuffer::append() method is quite expensive compared to a
217         // simple array operation, so it pays here to copy the final result
218         // instead.
219 
220         // Allocate the max possible buffer. Try to use stack instead of heap,
221         // which would have to be reallocated most times anyways.
222         const sal_Int32 nLocalBuf = 2048;
223         sal_Unicode aLocalBuf[ nLocalBuf * NMAPPINGMAX ], *out = aLocalBuf, *pHeapBuf = NULL;
224         if ( nCount > nLocalBuf )
225             out = pHeapBuf = new sal_Unicode[ nCount * NMAPPINGMAX ];
226 
227         sal_Int32 j = 0;
228         for ( sal_Int32 i = 0; i < nCount; i++)
229         {
230             // take care of TOGGLE_CASE transliteration:
231             sal_uInt8 nTmpMappingType = nMappingType;
232             if (nMappingType == (MappingTypeLowerToUpper | MappingTypeUpperToLower))
233                 nTmpMappingType = lcl_getMappingTypeForToggleCase( nMappingType, in[i] );
234 
235             const Mapping &map = casefolding::getValue( in, i, nCount, aLocale, nTmpMappingType );
236             for (sal_Int32 k = 0; k < map.nmap; k++)
237             {
238                 out[j++] = map.map[k];
239             }
240         }
241 
242         OUString aRet( out, j );
243         if ( pHeapBuf )
244             delete [] pHeapBuf;
245         return aRet;
246     }
247 #endif
248 }
249 
250 OUString SAL_CALL
transliterateChar2String(sal_Unicode inChar)251 Transliteration_body::transliterateChar2String( sal_Unicode inChar )
252 {
253         const Mapping &map = casefolding::getValue(&inChar, 0, 1, aLocale, nMappingType);
254         rtl_uString* pStr = x_rtl_uString_new_WithLength( map.nmap );  // our x_rtl_ustring.h
255         sal_Unicode* out = pStr->buffer;
256         sal_Int32 i;
257 
258         for (i = 0; i < map.nmap; i++)
259             out[i] = map.map[i];
260         out[i] = 0;
261 
262         return OUString( pStr, SAL_NO_ACQUIRE ); // take over ownership of <pStr>
263 }
264 
265 sal_Unicode SAL_CALL
transliterateChar2Char(sal_Unicode inChar)266 Transliteration_body::transliterateChar2Char( sal_Unicode inChar )
267 {
268         const Mapping &map = casefolding::getValue(&inChar, 0, 1, aLocale, nMappingType);
269         if (map.nmap > 1)
270             throw MultipleCharsOutputException();
271         return map.map[0];
272 }
273 
274 OUString SAL_CALL
folding(const OUString & inStr,sal_Int32 startPos,sal_Int32 nCount,Sequence<sal_Int32> & offset)275 Transliteration_body::folding( const OUString& inStr, sal_Int32 startPos, sal_Int32 nCount,
276     Sequence< sal_Int32 >& offset)
277 {
278     return this->transliterate(inStr, startPos, nCount, offset);
279 }
280 
Transliteration_casemapping()281 Transliteration_casemapping::Transliteration_casemapping()
282 {
283     nMappingType = 0;
284     transliterationName = "casemapping(generic)";
285     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_casemapping";
286 }
287 
288 void SAL_CALL
setMappingType(const sal_uInt8 rMappingType,const Locale & rLocale)289 Transliteration_casemapping::setMappingType( const sal_uInt8 rMappingType, const Locale& rLocale )
290 {
291     nMappingType = rMappingType;
292     aLocale = rLocale;
293 }
294 
Transliteration_u2l()295 Transliteration_u2l::Transliteration_u2l()
296 {
297     nMappingType = MappingTypeUpperToLower;
298     transliterationName = "upper_to_lower(generic)";
299     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_u2l";
300 }
301 
Transliteration_l2u()302 Transliteration_l2u::Transliteration_l2u()
303 {
304     nMappingType = MappingTypeLowerToUpper;
305     transliterationName = "lower_to_upper(generic)";
306     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_l2u";
307 }
308 
Transliteration_togglecase()309 Transliteration_togglecase::Transliteration_togglecase()
310 {
311     // usually nMappingType must NOT be a combiantion of different flages here,
312     // but we take care of that problem in Transliteration_body::transliterate above
313     // before that value is used. There we will decide which of both is to be used on
314     // a per character basis.
315     nMappingType = MappingTypeLowerToUpper | MappingTypeUpperToLower;
316     transliterationName = "toggle(generic)";
317     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_togglecase";
318 }
319 
Transliteration_titlecase()320 Transliteration_titlecase::Transliteration_titlecase()
321 {
322     nMappingType = MappingTypeToTitle;
323     transliterationName = "title(generic)";
324     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_titlecase";
325 }
326 
327 #if 0
328 struct LigatureData
329 {
330     sal_uInt32  cChar;
331     sal_Char *  pUtf8Text;
332 };
333 
334 // available Unicode ligatures:
335 // http://www.unicode.org/charts
336 // http://www.unicode.org/charts/PDF/UFB00.pdf
337 static LigatureData aLigatures[] =
338 {
339     { 0x0FB00,     "ff" },
340     { 0x0FB01,     "fi" },
341     { 0x0FB02,     "fl" },
342     { 0x0FB03,     "ffi" },
343     { 0x0FB04,     "ffl" },
344     { 0x0FB05,     "ft" },
345     { 0x0FB06,     "st" },
346 
347     { 0x0FB13,     "\xD5\xB4\xD5\xB6" },     // Armenian small men now
348     { 0x0FB14,     "\xD5\xB4\xD5\xA5" },     // Armenian small men ech
349     { 0x0FB15,     "\xD5\xB4\xD5\xAB" },     // Armenian small men ini
350     { 0x0FB16,     "\xD5\xBE\xD5\xB6" },     // Armenian small vew now
351     { 0x0FB17,     "\xD5\xB4\xD5\xAD" },     // Armenian small men xeh
352     { 0x00000,     "" }
353 };
354 
355 static inline bool lcl_IsLigature( sal_uInt32 cChar )
356 {
357     return (0x0FB00 <= cChar && cChar <= 0x0FB06) || (0x0FB13 <= cChar && cChar <= 0x0FB17);
358 }
359 
360 static rtl::OUString lcl_ResolveLigature( sal_uInt32 cChar )
361 {
362     rtl::OUString aRes;
363     if (lcl_IsLigature( cChar ))
364     {
365         LigatureData *pFound = NULL;
366         LigatureData *pData = aLigatures;
367         while (!pFound && pData->cChar != 0)
368         {
369             if (pData->cChar == cChar)
370                 pFound = pData;
371             ++pData;
372         }
373         if (pFound)
374             aRes = rtl::OUString( pFound->pUtf8Text, strlen( pFound->pUtf8Text ), RTL_TEXTENCODING_UTF8 );
375     }
376     else
377         aRes = rtl::OUString( &cChar, 1 );
378     return aRes;
379 }
380 #endif // if 0
381 
transliterate_titlecase_Impl(const OUString & inStr,sal_Int32 startPos,sal_Int32 nCount,const Locale & rLocale,Sequence<sal_Int32> & offset)382 static rtl::OUString transliterate_titlecase_Impl(
383     const OUString& inStr, sal_Int32 startPos, sal_Int32 nCount,
384     const Locale &rLocale,
385     Sequence< sal_Int32 >& offset )
386 {
387     const OUString aText( inStr.copy( startPos, nCount ) );
388 
389     OUString aRes;
390     if (aText.getLength() > 0)
391     {
392         Reference< XMultiServiceFactory > xMSF = ::comphelper::getProcessServiceFactory();
393         CharacterClassificationImpl aCharClassImpl( xMSF );
394 
395         // because aCharClassImpl.toTitle does not handle ligatures or � but will raise
396         // an exception we need to handle the first chara manually...
397 
398         // we don't want to change surrogates by accident, thuse we use proper code point iteration
399         sal_Int32 nPos = 0;
400         sal_uInt32 cFirstChar = aText.iterateCodePoints( &nPos );
401         OUString aResolvedLigature( &cFirstChar, 1 ); //lcl_ResolveLigature( cFirstChar ) );
402         // toUpper can be used to properly resolve ligatures and characters like �
403         aResolvedLigature = aCharClassImpl.toUpper( aResolvedLigature, 0, aResolvedLigature.getLength(), rLocale );
404         // since toTitle will leave all-uppercase text unchanged we first need to
405         // use toLower to bring possible 2nd and following charas in lowercase
406         aResolvedLigature = aCharClassImpl.toLower( aResolvedLigature, 0, aResolvedLigature.getLength(), rLocale );
407         sal_Int32 nResolvedLen = aResolvedLigature.getLength();
408 
409         // now we can properly use toTitle to get the expected result for the resolved string.
410         // The rest of the text should just become lowercase.
411         aRes = aCharClassImpl.toTitle( aResolvedLigature, 0, nResolvedLen, rLocale );
412         aRes += aCharClassImpl.toLower( aText, 1, aText.getLength() - 1, rLocale );
413         offset.realloc( aRes.getLength() );
414 
415         sal_Int32 *pOffset = offset.getArray();
416         sal_Int32 nLen = offset.getLength();
417         for (sal_Int32 i = 0; i < nLen; ++i)
418         {
419             sal_Int32 nIdx = 0;
420             if (i >= nResolvedLen)
421                 nIdx = i - nResolvedLen + 1;
422             pOffset[i] = nIdx;
423         }
424     }
425 #if OSL_DEBUG_LEVEL > 1
426     const sal_Int32 *pCOffset = offset.getConstArray();
427     (void) pCOffset;
428 #endif
429 
430     return aRes;
431 }
432 
433 
434 // this function expects to be called on a word-by-word basis,
435 // namely that startPos points to the first char of the word
transliterate(const OUString & inStr,sal_Int32 startPos,sal_Int32 nCount,Sequence<sal_Int32> & offset)436 rtl::OUString SAL_CALL Transliteration_titlecase::transliterate(
437     const OUString& inStr, sal_Int32 startPos, sal_Int32 nCount,
438     Sequence< sal_Int32 >& offset )
439 {
440     return transliterate_titlecase_Impl( inStr, startPos, nCount, aLocale, offset );
441 }
442 
443 
Transliteration_sentencecase()444 Transliteration_sentencecase::Transliteration_sentencecase()
445 {
446     nMappingType = MappingTypeToTitle;  // though only to be applied to the first word...
447     transliterationName = "sentence(generic)";
448     implementationName = "com.sun.star.i18n.Transliteration.Transliteration_sentencecase";
449 }
450 
451 
452 // this function expects to be called on a sentence-by-sentence basis,
453 // namely that startPos points to the first word (NOT first char!) in the sentence
transliterate(const OUString & inStr,sal_Int32 startPos,sal_Int32 nCount,Sequence<sal_Int32> & offset)454 rtl::OUString SAL_CALL Transliteration_sentencecase::transliterate(
455     const OUString& inStr, sal_Int32 startPos, sal_Int32 nCount,
456     Sequence< sal_Int32 >& offset )
457 {
458     return transliterate_titlecase_Impl( inStr, startPos, nCount, aLocale, offset );
459 }
460 
461 
462 } } } }
463