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