Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <comphelper/string.hxx>
21 : #include <cppuhelper/implbase1.hxx>
22 : #include <com/sun/star/i18n/CharType.hpp>
23 :
24 : #include "cppunit/TestAssert.h"
25 : #include "cppunit/TestFixture.h"
26 : #include "cppunit/extensions/HelperMacros.h"
27 : #include "cppunit/plugin/TestPlugIn.h"
28 : #include <rtl/string.hxx>
29 : #include <rtl/ustring.hxx>
30 :
31 : namespace {
32 :
33 36 : class TestString: public CppUnit::TestFixture
34 : {
35 : public:
36 : void testNatural();
37 : void testRemove();
38 : void testStripStart();
39 : void testStripEnd();
40 : void testStrip();
41 : void testToken();
42 : void testTokenCount();
43 : void testDecimalStringToNumber();
44 : void testIsdigitAsciiString();
45 : void testReverseString();
46 : void testEqualsString();
47 : void testCompareVersionStrings();
48 :
49 2 : CPPUNIT_TEST_SUITE(TestString);
50 1 : CPPUNIT_TEST(testNatural);
51 1 : CPPUNIT_TEST(testRemove);
52 1 : CPPUNIT_TEST(testStripStart);
53 1 : CPPUNIT_TEST(testStripEnd);
54 1 : CPPUNIT_TEST(testStrip);
55 1 : CPPUNIT_TEST(testToken);
56 1 : CPPUNIT_TEST(testTokenCount);
57 1 : CPPUNIT_TEST(testDecimalStringToNumber);
58 1 : CPPUNIT_TEST(testIsdigitAsciiString);
59 1 : CPPUNIT_TEST(testReverseString);
60 1 : CPPUNIT_TEST(testEqualsString);
61 1 : CPPUNIT_TEST(testCompareVersionStrings);
62 5 : CPPUNIT_TEST_SUITE_END();
63 : };
64 :
65 1 : void TestString::testDecimalStringToNumber()
66 : {
67 1 : OUString s1("1234");
68 1 : CPPUNIT_ASSERT_EQUAL((sal_uInt32)1234, comphelper::string::decimalStringToNumber(s1));
69 1 : s1 += OUString(static_cast<sal_Unicode>(0x07C6));
70 1 : CPPUNIT_ASSERT_EQUAL((sal_uInt32)12346, comphelper::string::decimalStringToNumber(s1));
71 : // Codepoints on 2 16bits words
72 1 : sal_uInt32 utf16String[] = { 0x1D7FE /* 8 */, 0x1D7F7 /* 1 */};
73 1 : s1 = OUString(utf16String, 2);
74 1 : CPPUNIT_ASSERT_EQUAL((sal_uInt32)81, comphelper::string::decimalStringToNumber(s1));
75 1 : }
76 :
77 1 : void TestString::testIsdigitAsciiString()
78 : {
79 1 : OString s1("1234");
80 1 : CPPUNIT_ASSERT_EQUAL(comphelper::string::isdigitAsciiString(s1), true);
81 :
82 2 : OString s2("1A34");
83 1 : CPPUNIT_ASSERT_EQUAL(comphelper::string::isdigitAsciiString(s2), false);
84 :
85 2 : OString s3;
86 2 : CPPUNIT_ASSERT_EQUAL(comphelper::string::isdigitAsciiString(s3), true);
87 1 : }
88 :
89 : using namespace ::com::sun::star;
90 :
91 3 : class testCollator : public cppu::WeakImplHelper1< i18n::XCollator >
92 : {
93 : public:
94 13 : virtual sal_Int32 SAL_CALL compareSubstring(
95 : const OUString& str1, sal_Int32 off1, sal_Int32 len1,
96 : const OUString& str2, sal_Int32 off2, sal_Int32 len2) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
97 : {
98 13 : return str1.copy(off1, len1).compareTo(str2.copy(off2, len2));
99 : }
100 0 : virtual sal_Int32 SAL_CALL compareString(
101 : const OUString& str1,
102 : const OUString& str2) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
103 : {
104 0 : return str1.compareTo(str2);
105 : }
106 0 : virtual sal_Int32 SAL_CALL loadDefaultCollator(const lang::Locale&, sal_Int32)
107 0 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE {return 0;}
108 0 : virtual sal_Int32 SAL_CALL loadCollatorAlgorithm(const OUString&,
109 0 : const lang::Locale&, sal_Int32) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE {return 0;}
110 0 : virtual void SAL_CALL loadCollatorAlgorithmWithEndUserOption(const OUString&,
111 0 : const lang::Locale&, const uno::Sequence< sal_Int32 >&) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE {}
112 0 : virtual uno::Sequence< OUString > SAL_CALL listCollatorAlgorithms(const lang::Locale&)
113 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
114 : {
115 0 : return uno::Sequence< OUString >();
116 : }
117 0 : virtual uno::Sequence< sal_Int32 > SAL_CALL listCollatorOptions(const OUString&)
118 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
119 : {
120 0 : return uno::Sequence< sal_Int32 >();
121 : }
122 : };
123 :
124 : #define IS_DIGIT(CHAR) (((CHAR) >= 48) && ((CHAR <= 57)))
125 :
126 3 : class testBreakIterator : public cppu::WeakImplHelper1< i18n::XBreakIterator >
127 : {
128 : public:
129 0 : virtual sal_Int32 SAL_CALL nextCharacters( const OUString&, sal_Int32,
130 : const lang::Locale&, sal_Int16, sal_Int32, sal_Int32& )
131 0 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE {return -1;}
132 0 : virtual sal_Int32 SAL_CALL previousCharacters( const OUString&, sal_Int32,
133 : const lang::Locale&, sal_Int16, sal_Int32, sal_Int32& )
134 0 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE {return -1;}
135 :
136 0 : virtual i18n::Boundary SAL_CALL previousWord( const OUString&, sal_Int32,
137 : const lang::Locale&, sal_Int16) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
138 0 : { return i18n::Boundary(); }
139 0 : virtual i18n::Boundary SAL_CALL nextWord( const OUString&, sal_Int32,
140 : const lang::Locale&, sal_Int16) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
141 0 : { return i18n::Boundary(); }
142 0 : virtual i18n::Boundary SAL_CALL getWordBoundary( const OUString&, sal_Int32,
143 : const lang::Locale&, sal_Int16, sal_Bool )
144 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
145 0 : { return i18n::Boundary(); }
146 :
147 0 : virtual sal_Bool SAL_CALL isBeginWord( const OUString&, sal_Int32,
148 : const lang::Locale&, sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
149 0 : { return false; }
150 0 : virtual sal_Bool SAL_CALL isEndWord( const OUString&, sal_Int32,
151 : const lang::Locale& , sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
152 0 : { return false; }
153 0 : virtual sal_Int16 SAL_CALL getWordType( const OUString&, sal_Int32,
154 : const lang::Locale& ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
155 0 : { return 0; }
156 :
157 0 : virtual sal_Int32 SAL_CALL beginOfSentence( const OUString&, sal_Int32,
158 : const lang::Locale& ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
159 0 : { return 0; }
160 0 : virtual sal_Int32 SAL_CALL endOfSentence( const OUString& rText, sal_Int32,
161 : const lang::Locale& ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
162 0 : { return rText.getLength(); }
163 :
164 0 : virtual i18n::LineBreakResults SAL_CALL getLineBreak( const OUString&, sal_Int32,
165 : const lang::Locale&, sal_Int32,
166 : const i18n::LineBreakHyphenationOptions&,
167 : const i18n::LineBreakUserOptions&)
168 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
169 : {
170 0 : return i18n::LineBreakResults();
171 : }
172 :
173 0 : virtual sal_Int16 SAL_CALL getScriptType( const OUString&, sal_Int32 )
174 0 : throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
175 0 : virtual sal_Int32 SAL_CALL beginOfScript( const OUString&, sal_Int32,
176 0 : sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
177 0 : virtual sal_Int32 SAL_CALL endOfScript( const OUString&, sal_Int32,
178 0 : sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
179 0 : virtual sal_Int32 SAL_CALL previousScript( const OUString&, sal_Int32,
180 0 : sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
181 0 : virtual sal_Int32 SAL_CALL nextScript( const OUString&, sal_Int32,
182 0 : sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
183 :
184 0 : virtual sal_Int32 SAL_CALL beginOfCharBlock( const OUString&, sal_Int32,
185 0 : const lang::Locale&, sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
186 18 : virtual sal_Int32 SAL_CALL endOfCharBlock( const OUString& rText, sal_Int32 nStartPos,
187 : const lang::Locale&, sal_Int16 CharType ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
188 : {
189 18 : const sal_Unicode *pStr = rText.getStr()+nStartPos;
190 44 : for (sal_Int32 nI = nStartPos; nI < rText.getLength(); ++nI)
191 : {
192 32 : if (CharType == i18n::CharType::DECIMAL_DIGIT_NUMBER && !IS_DIGIT(*pStr))
193 6 : return nI;
194 26 : else if (CharType != i18n::CharType::DECIMAL_DIGIT_NUMBER && IS_DIGIT(*pStr))
195 0 : return nI;
196 26 : ++pStr;
197 : }
198 12 : return -1;
199 : }
200 0 : virtual sal_Int32 SAL_CALL previousCharBlock( const OUString&, sal_Int32,
201 0 : const lang::Locale&, sal_Int16 ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE { return -1; }
202 26 : virtual sal_Int32 SAL_CALL nextCharBlock( const OUString& rText, sal_Int32 nStartPos,
203 : const lang::Locale&, sal_Int16 CharType ) throw(uno::RuntimeException, std::exception) SAL_OVERRIDE
204 : {
205 26 : const sal_Unicode *pStr = rText.getStr()+nStartPos;
206 196 : for (sal_Int32 nI = nStartPos; nI < rText.getLength(); ++nI)
207 : {
208 184 : if (CharType == i18n::CharType::DECIMAL_DIGIT_NUMBER && IS_DIGIT(*pStr))
209 14 : return nI;
210 170 : else if (CharType != i18n::CharType::DECIMAL_DIGIT_NUMBER && !IS_DIGIT(*pStr))
211 0 : return nI;
212 170 : ++pStr;
213 : }
214 12 : return -1;
215 : }
216 : };
217 :
218 1 : void TestString::testNatural()
219 : {
220 : using namespace comphelper::string;
221 :
222 1 : uno::Reference< i18n::XCollator > xCollator(new testCollator);
223 2 : uno::Reference< i18n::XBreakIterator > xBI(new testBreakIterator);
224 :
225 : // --- Some generic tests to ensure we do not alter original behavior
226 : // outside what we want
227 2 : CPPUNIT_ASSERT(
228 : compareNatural("ABC", "ABC", xCollator, xBI, lang::Locale()) == 0
229 1 : );
230 : // Case sensitivity
231 2 : CPPUNIT_ASSERT(
232 : compareNatural("ABC", "abc", xCollator, xBI, lang::Locale()) < 0
233 1 : );
234 : // Reverse
235 2 : CPPUNIT_ASSERT(
236 : compareNatural("abc", "ABC", xCollator, xBI, lang::Locale()) > 0
237 1 : );
238 : // First shorter
239 2 : CPPUNIT_ASSERT(
240 : compareNatural("alongstring", "alongerstring", xCollator, xBI, lang::Locale()) > 0
241 1 : );
242 : // Second shorter
243 2 : CPPUNIT_ASSERT(
244 : compareNatural("alongerstring", "alongstring", xCollator, xBI, lang::Locale()) < 0
245 1 : );
246 : // -- Here we go on natural order, each one is followed by classic compare and the reverse comparison
247 : // That's why we originally made the patch
248 2 : CPPUNIT_ASSERT(
249 : compareNatural("Heading 9", "Heading 10", xCollator, xBI, lang::Locale()) < 0
250 1 : );
251 : // Original behavior
252 2 : CPPUNIT_ASSERT(
253 : OUString("Heading 9").compareTo("Heading 10") > 0
254 1 : );
255 2 : CPPUNIT_ASSERT(
256 : compareNatural("Heading 10", "Heading 9", xCollator, xBI, lang::Locale()) > 0
257 1 : );
258 : // Harder
259 2 : CPPUNIT_ASSERT(
260 : compareNatural("July, the 4th", "July, the 10th", xCollator, xBI, lang::Locale()) < 0
261 1 : );
262 2 : CPPUNIT_ASSERT(
263 : OUString("July, the 4th").compareTo("July, the 10th") > 0
264 1 : );
265 2 : CPPUNIT_ASSERT(
266 : compareNatural("July, the 10th", "July, the 4th", xCollator, xBI, lang::Locale()) > 0
267 1 : );
268 : // Hardest
269 2 : CPPUNIT_ASSERT(
270 : compareNatural("abc08", "abc010", xCollator, xBI, lang::Locale()) < 0
271 1 : );
272 2 : CPPUNIT_ASSERT(
273 : OUString("abc08").compareTo("abc010") > 0
274 1 : );
275 2 : CPPUNIT_ASSERT(
276 : compareNatural("abc010", "abc08", xCollator, xBI, lang::Locale()) > 0
277 1 : );
278 2 : CPPUNIT_ASSERT(
279 : compareNatural("apple10apple", "apple10apple", xCollator, xBI, lang::Locale()) == 0
280 2 : );
281 1 : }
282 :
283 1 : void TestString::testRemove()
284 : {
285 1 : OString aIn("abc");
286 2 : OString aOut;
287 :
288 1 : aOut = ::comphelper::string::remove(aIn, 'b');
289 1 : CPPUNIT_ASSERT(aOut == "ac");
290 :
291 1 : aIn = "aaa";
292 :
293 1 : aOut = ::comphelper::string::remove(aIn, 'a');
294 2 : CPPUNIT_ASSERT(aOut.isEmpty());
295 1 : }
296 :
297 1 : void TestString::testStripStart()
298 : {
299 1 : OString aIn("abc");
300 2 : OString aOut;
301 :
302 1 : aOut = ::comphelper::string::stripStart(aIn, 'b');
303 1 : CPPUNIT_ASSERT(aOut == "abc");
304 :
305 1 : aOut = ::comphelper::string::stripStart(aIn, 'a');
306 1 : CPPUNIT_ASSERT(aOut == "bc");
307 :
308 1 : aIn = "aaa";
309 1 : aOut = ::comphelper::string::stripStart(aIn, 'a');
310 1 : CPPUNIT_ASSERT(aOut.isEmpty());
311 :
312 1 : aIn = "aba";
313 1 : aOut = ::comphelper::string::stripStart(aIn, 'a');
314 2 : CPPUNIT_ASSERT(aOut == "ba");
315 1 : }
316 :
317 1 : void TestString::testStripEnd()
318 : {
319 1 : OString aIn("abc");
320 2 : OString aOut;
321 :
322 1 : aOut = ::comphelper::string::stripEnd(aIn, 'b');
323 1 : CPPUNIT_ASSERT(aOut == "abc");
324 :
325 1 : aOut = ::comphelper::string::stripEnd(aIn, 'c');
326 1 : CPPUNIT_ASSERT(aOut == "ab");
327 :
328 1 : aIn = "aaa";
329 1 : aOut = ::comphelper::string::stripEnd(aIn, 'a');
330 1 : CPPUNIT_ASSERT(aOut.isEmpty());
331 :
332 1 : aIn = "aba";
333 1 : aOut = ::comphelper::string::stripEnd(aIn, 'a');
334 2 : CPPUNIT_ASSERT(aOut == "ab");
335 1 : }
336 :
337 1 : void TestString::testStrip()
338 : {
339 1 : OString aIn("abc");
340 2 : OString aOut;
341 :
342 1 : aOut = ::comphelper::string::strip(aIn, 'b');
343 1 : CPPUNIT_ASSERT(aOut == "abc");
344 :
345 1 : aOut = ::comphelper::string::strip(aIn, 'c');
346 1 : CPPUNIT_ASSERT(aOut == "ab");
347 :
348 1 : aIn = "aaa";
349 1 : aOut = ::comphelper::string::strip(aIn, 'a');
350 1 : CPPUNIT_ASSERT(aOut.isEmpty());
351 :
352 1 : aIn = "aba";
353 1 : aOut = ::comphelper::string::strip(aIn, 'a');
354 2 : CPPUNIT_ASSERT(aOut == "b");
355 1 : }
356 :
357 1 : void TestString::testToken()
358 : {
359 1 : OString aIn("10.11.12");
360 2 : OString aOut;
361 :
362 1 : aOut = aIn.getToken(-1, '.');
363 1 : CPPUNIT_ASSERT(aOut.isEmpty());
364 :
365 1 : aOut = aIn.getToken(0, '.');
366 1 : CPPUNIT_ASSERT(aOut == "10");
367 :
368 1 : aOut = aIn.getToken(1, '.');
369 1 : CPPUNIT_ASSERT(aOut == "11");
370 :
371 1 : aOut = aIn.getToken(2, '.');
372 1 : CPPUNIT_ASSERT(aOut == "12");
373 :
374 1 : aOut = aIn.getToken(3, '.');
375 2 : CPPUNIT_ASSERT(aOut.isEmpty());
376 1 : }
377 :
378 1 : void TestString::testTokenCount()
379 : {
380 1 : OString aIn("10.11.12");
381 : sal_Int32 nOut;
382 :
383 1 : nOut = ::comphelper::string::getTokenCount(aIn, '.');
384 1 : CPPUNIT_ASSERT(nOut == 3);
385 :
386 1 : nOut = ::comphelper::string::getTokenCount(aIn, 'X');
387 1 : CPPUNIT_ASSERT(nOut == 1);
388 :
389 1 : nOut = ::comphelper::string::getTokenCount(OString(), 'X');
390 1 : CPPUNIT_ASSERT(nOut == 0);
391 1 : }
392 :
393 1 : void TestString::testReverseString()
394 : {
395 1 : OString aIn("ABC");
396 2 : OString aOut = ::comphelper::string::reverseString(aIn);
397 :
398 2 : CPPUNIT_ASSERT(aOut == "CBA");
399 1 : }
400 :
401 1 : void TestString::testEqualsString()
402 : {
403 1 : OString aIn("A");
404 1 : CPPUNIT_ASSERT(::comphelper::string::equals(aIn, 'A'));
405 1 : CPPUNIT_ASSERT(!::comphelper::string::equals(aIn, 'B'));
406 1 : aIn = OString("AA");
407 1 : CPPUNIT_ASSERT(!::comphelper::string::equals(aIn, 'A'));
408 1 : aIn.clear();
409 1 : CPPUNIT_ASSERT(!::comphelper::string::equals(aIn, 'A'));
410 1 : }
411 :
412 35 : int sign(int n)
413 : {
414 35 : if (n == 0)
415 1 : return 0;
416 34 : if (n < 0)
417 17 : return -1;
418 : else
419 17 : return 1;
420 : }
421 :
422 1 : void TestString::testCompareVersionStrings()
423 : {
424 : #ifdef TEST
425 : #error TEST already defined
426 : #endif
427 : #define TEST(a,b,result) \
428 : CPPUNIT_ASSERT(sign(::comphelper::string::compareVersionStrings(a, b)) == result); \
429 : if ( result != 0 ) \
430 : CPPUNIT_ASSERT(sign(::comphelper::string::compareVersionStrings(b, a)) == -(result))
431 :
432 1 : TEST("", "", 0);
433 1 : TEST("", "0", -1);
434 1 : TEST("", "a", -1);
435 1 : TEST("0", "1", -1);
436 1 : TEST("1", "2", -1);
437 1 : TEST("2", "10", -1);
438 1 : TEST("01", "1", -1);
439 1 : TEST("01", "001", 1);
440 1 : TEST("1.00", "1", 1);
441 1 : TEST("1.2", "1", 1);
442 1 : TEST("1.01", "1.1", -1);
443 1 : TEST("1.001", "1.1", -1);
444 1 : TEST("1.001", "1.010", -1);
445 1 : TEST("1.2.a", "1.2.b", -1);
446 1 : TEST("1.2.3 (foo,bar)", "1.2.9", -1);
447 1 : TEST("1.2.3 (foo,bar)", "1.2.4 (foo,bar)", -1);
448 1 : TEST("1.2.3 (foo,bar)", "1.2.3 (foo)", 1); // Neither ordering makes any more sense than the other here, as long as they compare unequal
449 1 : TEST("1.2.3 (foo,bar)", "1.2.2 (foo,bar)", 1);
450 :
451 : #undef TEST
452 1 : }
453 :
454 1 : CPPUNIT_TEST_SUITE_REGISTRATION(TestString);
455 :
456 : }
457 :
458 4 : CPPUNIT_PLUGIN_IMPLEMENT();
459 :
460 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|