diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svl/qa | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svl/qa')
-rw-r--r-- | svl/qa/complex/ConfigItems/CheckConfigItems.java | 156 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/MasterPasswdHandler.java | 73 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/PasswordContainerTest.java | 23 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/PasswordContainerUnitTest.java | 84 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/Test01.java | 98 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/Test02.java | 144 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/Test03.java | 108 | ||||
-rw-r--r-- | svl/qa/complex/passwordcontainer/TestHelper.java | 78 | ||||
-rw-r--r-- | svl/qa/unit/items/stylepool.cxx | 84 | ||||
-rw-r--r-- | svl/qa/unit/items/test_IndexedStyleSheets.cxx | 210 | ||||
-rw-r--r-- | svl/qa/unit/items/test_itempool.cxx | 108 | ||||
-rw-r--r-- | svl/qa/unit/lockfiles/test_lockfiles.cxx | 706 | ||||
-rw-r--r-- | svl/qa/unit/notify/test_SfxBroadcaster.cxx | 118 | ||||
-rw-r--r-- | svl/qa/unit/svl.cxx | 2002 | ||||
-rw-r--r-- | svl/qa/unit/test_INetContentType.cxx | 89 | ||||
-rw-r--r-- | svl/qa/unit/test_SvAddressParser.cxx | 77 | ||||
-rw-r--r-- | svl/qa/unit/test_URIHelper.cxx | 528 | ||||
-rw-r--r-- | svl/qa/unit/test_lngmisc.cxx | 168 |
18 files changed, 4854 insertions, 0 deletions
diff --git a/svl/qa/complex/ConfigItems/CheckConfigItems.java b/svl/qa/complex/ConfigItems/CheckConfigItems.java new file mode 100644 index 0000000000..6503505f3a --- /dev/null +++ b/svl/qa/complex/ConfigItems/CheckConfigItems.java @@ -0,0 +1,156 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ConfigItems; + +import com.sun.star.beans.NamedValue; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.task.XJob; +import com.sun.star.uno.UnoRuntime; + + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; + + +/** @short todo document me + * @deprecated this tests seems no longer work as expected. + */ +@Deprecated +public class CheckConfigItems +{ + + // some const + + + // member + + /** points to the global uno service manager. */ + private XMultiServiceFactory m_xSmgr = null; + + /** implements real config item tests in C++. */ + private XJob m_xTest = null; + + + // test environment + + + /** @short Create the environment for following tests. + + * @descr Use either a component loader from desktop or + from frame + */ + @Before public void before() + throws java.lang.Exception + { + // get uno service manager from global test environment + m_xSmgr = getMSF(); + + // TODO register helper service + + // create module manager + m_xTest = UnoRuntime.queryInterface(XJob.class, m_xSmgr.createInstance("com.sun.star.comp.svl.ConfigItemTest")); + } + + + /** + * @short close the environment. + */ + @After public void after() + throws java.lang.Exception + { + // TODO deregister helper service + + m_xTest = null; + m_xSmgr = null; + } + + + @Test public void checkPicklist() + throws java.lang.Exception + { + impl_triggerTest("checkPicklist"); + } + + + @Test public void checkURLHistory() + throws java.lang.Exception + { + impl_triggerTest("checkURLHistory"); + } + + + @Test public void checkHelpBookmarks() + throws java.lang.Exception + { + impl_triggerTest("checkHelpBookmarks"); + } + + + @Test public void checkAccessibilityOptions() + throws java.lang.Exception + { + impl_triggerTest("checkAccessibilityOptions"); + } + + + @Test public void checkUserOptions() + throws java.lang.Exception + { + impl_triggerTest("checkUserOptions"); + } + + + /** @todo document me + */ + private void impl_triggerTest(String sTest) + throws java.lang.Exception + { + NamedValue[] lArgs = new NamedValue[1]; + lArgs[0] = new NamedValue(); + lArgs[0].Name = "Test"; + lArgs[0].Value = sTest; + m_xTest.execute(lArgs); + } + + + private XMultiServiceFactory getMSF() + { + return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); + } + + // setup and close connections + @BeforeClass public static void setUpConnection() throws Exception { + System.out.println("setUpConnection()"); + connection.setUp(); + } + + @AfterClass public static void tearDownConnection() + throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println("tearDownConnection()"); + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} diff --git a/svl/qa/complex/passwordcontainer/MasterPasswdHandler.java b/svl/qa/complex/passwordcontainer/MasterPasswdHandler.java new file mode 100644 index 0000000000..9c9eec41ce --- /dev/null +++ b/svl/qa/complex/passwordcontainer/MasterPasswdHandler.java @@ -0,0 +1,73 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.task.XInteractionContinuation; +import com.sun.star.ucb.XInteractionSupplyAuthentication; +import com.sun.star.task.XInteractionRequest; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.task.MasterPasswordRequest; +import com.sun.star.uno.UnoRuntime; + +public class MasterPasswdHandler extends WeakBase + implements XInteractionHandler { + private final XInteractionHandler m_xHandler; + + public MasterPasswdHandler( XInteractionHandler xHandler ) { + m_xHandler = xHandler; + } + + public void handle( XInteractionRequest xRequest ) { + try { + MasterPasswordRequest aMasterPasswordRequest; + if( xRequest.getRequest() instanceof MasterPasswordRequest ) { + aMasterPasswordRequest = (MasterPasswordRequest)xRequest.getRequest(); + if( aMasterPasswordRequest != null ) { + XInteractionContinuation xContinuations[] = xRequest.getContinuations(); + XInteractionSupplyAuthentication xAuthentication = null; + + for( int i = 0; i < xContinuations.length; ++i ) { + xAuthentication = UnoRuntime.queryInterface(XInteractionSupplyAuthentication.class, xContinuations[i]); + if( xAuthentication != null ) + { + break; + } + } + if( xAuthentication.canSetPassword() ) + { + xAuthentication.setPassword("abcdefghijklmnopqrstuvwxyz123456"); + } + xAuthentication.select(); + } + } else { + m_xHandler.handle( xRequest ); + } + } catch( Exception e ) { + System.out.println( "MasterPasswordHandler Error: " + e ); + } + } +} + + + + + + + diff --git a/svl/qa/complex/passwordcontainer/PasswordContainerTest.java b/svl/qa/complex/passwordcontainer/PasswordContainerTest.java new file mode 100644 index 0000000000..b96a0986d0 --- /dev/null +++ b/svl/qa/complex/passwordcontainer/PasswordContainerTest.java @@ -0,0 +1,23 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +interface PasswordContainerTest { + boolean test(); +} diff --git a/svl/qa/complex/passwordcontainer/PasswordContainerUnitTest.java b/svl/qa/complex/passwordcontainer/PasswordContainerUnitTest.java new file mode 100644 index 0000000000..1de047a369 --- /dev/null +++ b/svl/qa/complex/passwordcontainer/PasswordContainerUnitTest.java @@ -0,0 +1,84 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; +import static org.junit.Assert.*; + +public class PasswordContainerUnitTest { + private XMultiServiceFactory m_xMSF = null; + + @Before public void before() { + try { + m_xMSF = getMSF(); + } catch (Exception e) { + fail ("Cannot create service factory!"); + } + if (m_xMSF == null) { + fail ("Cannot create service factory!"); + } + } + + @After public void after() { + m_xMSF = null; + } + + @Test public void ExecuteTest01() + { + PasswordContainerTest aTest = new Test01(m_xMSF); + assertTrue("Test01 failed!", aTest.test()); + } + @Test public void ExecuteTest02() { + PasswordContainerTest aTest = new Test02(m_xMSF); + assertTrue("Test02 failed!", aTest.test()); + } + @Test public void ExecuteTest03() { + PasswordContainerTest aTest = new Test03(m_xMSF); + assertTrue("Test03 failed!", aTest.test()); + } + + private XMultiServiceFactory getMSF() + { + return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); + } + + // setup and close connections + @BeforeClass public static void setUpConnection() throws Exception { + System.out.println("setUpConnection()"); + connection.setUp(); + } + + @AfterClass public static void tearDownConnection() + throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println("tearDownConnection()"); + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} diff --git a/svl/qa/complex/passwordcontainer/Test01.java b/svl/qa/complex/passwordcontainer/Test01.java new file mode 100644 index 0000000000..094259b1bf --- /dev/null +++ b/svl/qa/complex/passwordcontainer/Test01.java @@ -0,0 +1,98 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.task.XPasswordContainer; +import com.sun.star.task.UrlRecord; +import com.sun.star.task.UserRecord; +import com.sun.star.uno.UnoRuntime; + + +public class Test01 implements PasswordContainerTest { + private XMultiServiceFactory m_xMSF = null; + private TestHelper m_aTestHelper = null; + + public Test01 ( XMultiServiceFactory xMSF ) + { + m_xMSF = xMSF; + m_aTestHelper = new TestHelper ( "Test01: "); + } + + public boolean test() { + final String sURL = "http://www.openoffice.org"; + final String sUserPre = "OOoUser"; + final String sPwdPre = "Password"; + final int iUserNum1 = 10; + final int iUserNum2 = 5; + + UserRecord aInputUserList1[] = new UserRecord[iUserNum1]; + for(int i = 0; i < iUserNum1; i++) { + String sTemp[] = {sPwdPre + "_1_" + i}; // currently one password for one user + aInputUserList1[i] = new UserRecord(sUserPre + "_1_" + i, sTemp); + } + UserRecord aInputUserList2[] = new UserRecord[iUserNum2]; + for(int i = 0; i < iUserNum2; i++) { + String sTemp[] = {sPwdPre + "_2_" + i}; + aInputUserList2[i] = new UserRecord(sUserPre + "_2_" + i, sTemp); + } + try { + Object oPasswordContainer = m_xMSF.createInstance( "com.sun.star.task.PasswordContainer" ); + XPasswordContainer xContainer = UnoRuntime.queryInterface(XPasswordContainer.class, oPasswordContainer); + Object oHandler = m_xMSF.createInstance( "com.sun.star.task.InteractionHandler" ); + XInteractionHandler xHandler = UnoRuntime.queryInterface(XInteractionHandler.class, oHandler); + MasterPasswdHandler aMHandler = new MasterPasswdHandler( xHandler ); + + // add a set of users and passwords for the same URL for runtime + for(int i = 0; i < iUserNum1; i++) { + xContainer.add(sURL, aInputUserList1[i].UserName, aInputUserList1[i].Passwords, aMHandler); + } + for (int i = 0; i < iUserNum2; i++) { + xContainer.add(sURL, aInputUserList2[i].UserName, aInputUserList2[i].Passwords, aMHandler); + } + + // remove some of the passwords + for (int i = 0; i < iUserNum1; i++) { + xContainer.remove(sURL, aInputUserList1[i].UserName); + } + + // get the result and check it with the expected one + UrlRecord aRecord = xContainer.find(sURL, aMHandler); + if(!aRecord.Url.equals(sURL)) { + m_aTestHelper.Error("URL mismatch. Got " + aRecord.Url + "; should be " + sURL); + return false; + } + if(!m_aTestHelper.sameLists(aRecord.UserList, aInputUserList2)) { + m_aTestHelper.Error("User list is not the expected"); + return false; + } + + // remove the runtime passwords + aRecord = xContainer.find(sURL, aMHandler); + for(int i = 0; i < aRecord.UserList.length; i++) { + xContainer.remove(sURL, aRecord.UserList[i].UserName); + } + } catch(Exception e) { + m_aTestHelper.Error("Exception: " + e); + return false; + } + return true; + } +} diff --git a/svl/qa/complex/passwordcontainer/Test02.java b/svl/qa/complex/passwordcontainer/Test02.java new file mode 100644 index 0000000000..caffd223ce --- /dev/null +++ b/svl/qa/complex/passwordcontainer/Test02.java @@ -0,0 +1,144 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.task.XPasswordContainer; +import com.sun.star.task.XMasterPasswordHandling; +import com.sun.star.task.XInteractionHandler; +import com.sun.star.task.UrlRecord; +import com.sun.star.task.UserRecord; + +import com.sun.star.uno.UnoRuntime; + + +public class Test02 implements PasswordContainerTest { + private XMultiServiceFactory m_xMSF = null; + private TestHelper m_aTestHelper = null; + + public Test02 ( XMultiServiceFactory xMSF ) + { + m_xMSF = xMSF; + m_aTestHelper = new TestHelper ( "Test02: "); + } + + public boolean test() { + final String sURL = "http://www.openoffice.org"; + final String sUserPre = "OOoUser"; + final String sPwdPre = "Password"; + final int iUserNum1 = 10; + final int iUserNum2 = 5; + + UserRecord aInputUserList1[] = new UserRecord[iUserNum1]; + for(int i = 0; i < iUserNum1; i++) { + String sTemp[] = {sPwdPre + "_1_" + i}; // currently one password for one user + aInputUserList1[i] = new UserRecord(sUserPre + "_1_" + i, sTemp); + } + UserRecord aInputUserList2[] = new UserRecord[iUserNum2]; + for(int i = 0; i < iUserNum2; i++) { + String sTemp[] = {sPwdPre + "_2_" + i}; + aInputUserList2[i] = new UserRecord(sUserPre + "_2_" + i, sTemp); + } + + try { + Object oPasswordContainer = m_xMSF.createInstance("com.sun.star.task.PasswordContainer"); + XPasswordContainer xContainer = UnoRuntime.queryInterface(XPasswordContainer.class, oPasswordContainer); + Object oHandler = m_xMSF.createInstance("com.sun.star.task.InteractionHandler"); + XInteractionHandler xHandler = UnoRuntime.queryInterface(XInteractionHandler.class, oHandler); + MasterPasswdHandler aMHandler = new MasterPasswdHandler(xHandler); + XMasterPasswordHandling xMHandling = UnoRuntime.queryInterface(XMasterPasswordHandling.class, oPasswordContainer); + + // allow the storing of the passwords + xMHandling.allowPersistentStoring(true); + + // add a set of users and passwords for the same URL persistently + for(int i = 0; i < iUserNum1; ++i) { + xContainer.addPersistent(sURL, aInputUserList1[i].UserName, aInputUserList1[i].Passwords, aMHandler); + } + for(int i = 0; i < iUserNum2; ++i) { + xContainer.addPersistent(sURL, aInputUserList2[i].UserName, aInputUserList2[i].Passwords, aMHandler); + } + + // remove some of the passwords + for(int i = 0; i < iUserNum1; ++i) { + xContainer.remove(sURL, aInputUserList1[i].UserName); + } + + // get the result with find() and check it with the expected one + UrlRecord aRecord = xContainer.find(sURL, aMHandler); + if(!aRecord.Url.equals(sURL)) { + m_aTestHelper.Error("URL mismatch. Got " + aRecord.Url + "; should be " + sURL); + return false; + } + if(!m_aTestHelper.sameLists(aRecord.UserList, aInputUserList2)) { + m_aTestHelper.Error("User list is not the expected"); + return false; + } + + // get the result with getAllPersistent() and check + UrlRecord aRecords[] = xContainer.getAllPersistent(aMHandler); + if(!aRecords[0].Url.equals(sURL)) { + m_aTestHelper.Error("URL mismatch"); + return false; + } + if(!m_aTestHelper.sameLists(aRecords[0].UserList, aInputUserList2)) { + m_aTestHelper.Error("User list is not the expected"); + return false; + } + + // remove all the persistent passwords + xContainer.removeAllPersistent(); + + // remove the runtime passwords + for(int i = 0; i < aRecords[0].UserList.length; ++i) { + xContainer.remove(sURL, aRecords[0].UserList[i].UserName); + } + + // disallow the storing of the passwords + xMHandling.allowPersistentStoring(false); + } catch(Exception e) { + m_aTestHelper.Error("Exception: " + e); + return false; + } + return true; + } +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/svl/qa/complex/passwordcontainer/Test03.java b/svl/qa/complex/passwordcontainer/Test03.java new file mode 100644 index 0000000000..fb49f4b99a --- /dev/null +++ b/svl/qa/complex/passwordcontainer/Test03.java @@ -0,0 +1,108 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.task.UrlRecord; +import com.sun.star.task.UserRecord; +import com.sun.star.task.XPasswordContainer; +import com.sun.star.task.XMasterPasswordHandling; +import com.sun.star.task.XInteractionHandler; + + +import com.sun.star.uno.UnoRuntime; + + +public class Test03 implements PasswordContainerTest { + private XMultiServiceFactory m_xMSF = null; + private TestHelper m_aTestHelper = null; + + public Test03 ( XMultiServiceFactory xMSF ) + { + m_xMSF = xMSF; + m_aTestHelper = new TestHelper ( "Test03: "); + } + + public boolean test() { + final String sURL = "http://www.openoffice.org"; + final String sUserPre = "OOoUser"; + final String sPwdPre = "Password"; + final int iPersistentUserNum = 10; + final int iRuntimeUserNum = 5; + + UserRecord aInputUserList[] = new UserRecord[iPersistentUserNum+iRuntimeUserNum]; + for(int i = 0; i < iPersistentUserNum; i++) { + String sTemp[] = {sPwdPre + "_1_" + i}; // currently one password for one user + aInputUserList[i] = new UserRecord(sUserPre + "_1_" + i, sTemp); + } + for(int i = 0; i < iRuntimeUserNum; i++) { + String sTemp[] = {sPwdPre + "_2_" + i}; + aInputUserList[i+iPersistentUserNum] = new UserRecord(sUserPre + "_2_" + i, sTemp); + } + + try { + Object oPasswordContainer = m_xMSF.createInstance("com.sun.star.task.PasswordContainer"); + XPasswordContainer xContainer = UnoRuntime.queryInterface(XPasswordContainer.class, oPasswordContainer); + Object oHandler = m_xMSF.createInstance("com.sun.star.task.InteractionHandler"); + XInteractionHandler xHandler = UnoRuntime.queryInterface(XInteractionHandler.class, oHandler); + MasterPasswdHandler aMHandler = new MasterPasswdHandler(xHandler); + XMasterPasswordHandling xMHandling = UnoRuntime.queryInterface(XMasterPasswordHandling.class, oPasswordContainer); + + // allow the storing of the passwords + xMHandling.allowPersistentStoring(true); + + // add a set of users and passwords for the same URL persistently + for(int i = 0; i < iPersistentUserNum; i++) { + xContainer.addPersistent(sURL, aInputUserList[i].UserName, aInputUserList[i].Passwords, aMHandler); + } + + // add a set of users and passwords for the same URL for runtime + for(int i = 0; i < iRuntimeUserNum; i++) { + xContainer.add(sURL, aInputUserList[i+iPersistentUserNum].UserName, aInputUserList[i+iPersistentUserNum].Passwords, aMHandler); + } + + // get the result for the URL and check that it contains persistent and runtime passwords + UrlRecord aRecord = xContainer.find(sURL, aMHandler); + if(!aRecord.Url.equals(sURL)) { + m_aTestHelper.Error("URL mismatch. Got " + aRecord.Url + "; should be " + sURL); + return false; + } + if(!m_aTestHelper.sameLists(aRecord.UserList, aInputUserList)) { + m_aTestHelper.Error("User list is not the expected"); + return false; + } + + // remove all the persistent passwords + xContainer.removeAllPersistent(); + + // remove the runtime passwords + aRecord = xContainer.find(sURL, aMHandler); + for(int i = 0; i < aRecord.UserList.length; i++) { + xContainer.remove(sURL, aRecord.UserList[i].UserName); + } + + // disallow the storing of the passwords + xMHandling.allowPersistentStoring(false); + }catch(Exception e){ + m_aTestHelper.Error("Exception: " + e); + return false; + } + return true; + } +} diff --git a/svl/qa/complex/passwordcontainer/TestHelper.java b/svl/qa/complex/passwordcontainer/TestHelper.java new file mode 100644 index 0000000000..b6f1f08425 --- /dev/null +++ b/svl/qa/complex/passwordcontainer/TestHelper.java @@ -0,0 +1,78 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.passwordcontainer; + +import com.sun.star.task.UserRecord; + + +public class TestHelper { + private String m_sTestPrefix; + + public TestHelper( String sTestPrefix ) { + m_sTestPrefix = sTestPrefix; + } + + public void Error( String sError ) { + System.out.println( m_sTestPrefix + "Error: " + sError ); + } + + private void Message( String sMessage ) { + System.out.println( m_sTestPrefix + sMessage ); + } + + public boolean sameLists(UserRecord aUserList1[], UserRecord aUserList2[]) { + // only works when every name is unique within the list containing it + + if(aUserList1.length != aUserList2.length) { + Message("User list lengths: " + aUserList1.length + " <--> " + aUserList2.length + " respectively "); + return false; + } + + for(int i = 0; i < aUserList1.length; i++) { + int j; + for(j = 0; j < aUserList2.length; j++) { + if(!aUserList1[i].UserName.equals(aUserList2[j].UserName)) + { + continue; + } + if(aUserList1[i].Passwords[0].equals(aUserList2[j].Passwords[0])) { + break; + } + } + if(j == aUserList2.length) { + for(int k = 0; k < aUserList1.length; k++) { + Message(aUserList1[k].UserName + " <--> " + aUserList2[i].UserName); + } + return false; + } + } + return true; + } +} + + + + + + + + + + + diff --git a/svl/qa/unit/items/stylepool.cxx b/svl/qa/unit/items/stylepool.cxx new file mode 100644 index 0000000000..d852dd29a2 --- /dev/null +++ b/svl/qa/unit/items/stylepool.cxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <unotest/bootstrapfixturebase.hxx> + +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <svl/stylepool.hxx> +#include <svl/stritem.hxx> + +namespace +{ +/// Tests svl StylePool. +class StylePoolTest : public CppUnit::TestFixture +{ +}; + +CPPUNIT_TEST_FIXTURE(StylePoolTest, testIterationOrder) +{ + // Set up a style pool with multiple parents. + SfxStringItem aDefault1(1); + std::vector<SfxPoolItem*> aDefaults{ &aDefault1 }; + SfxItemInfo const aItems[] = { // _nSID, _bNeedsPoolRegistration, _bShareable + { 2, false, false } + }; + + rtl::Reference<SfxItemPool> pPool = new SfxItemPool("test", 1, 1, aItems); + pPool->SetDefaults(&aDefaults); + { + // Set up parents in mixed order to make sure we do not sort by pointer address. + SfxItemSet aParent1(*pPool, svl::Items<1, 1>); + SfxItemSet aChild1(*pPool, svl::Items<1, 1>); + aChild1.SetParent(&aParent1); + SfxStringItem aItem1(1, "Item1"); + aChild1.Put(aItem1); + + SfxItemSet aParent3(*pPool, svl::Items<1, 1>); + SfxItemSet aChild3(*pPool, svl::Items<1, 1>); + aChild3.SetParent(&aParent3); + SfxStringItem aItem3(1, "Item3"); + aChild3.Put(aItem3); + + SfxItemSet aParent2(*pPool, svl::Items<1, 1>); + SfxItemSet aChild2(*pPool, svl::Items<1, 1>); + aChild2.SetParent(&aParent2); + SfxStringItem aItem2(1, "Item2"); + aChild2.Put(aItem2); + + // Insert item sets in alphabetical order. + StylePool aStylePool; + OUString aChild1Name("Child1"); + aStylePool.insertItemSet(aChild1, &aChild1Name); + OUString aChild3Name("Child3"); + aStylePool.insertItemSet(aChild3, &aChild3Name); + OUString aChild2Name("Child2"); + aStylePool.insertItemSet(aChild2, &aChild2Name); + std::unique_ptr<IStylePoolIteratorAccess> pIter = aStylePool.createIterator(); + std::shared_ptr<SfxItemSet> pStyle1 = pIter->getNext(); + CPPUNIT_ASSERT(pStyle1); + const SfxStringItem* pItem1 = static_cast<const SfxStringItem*>(pStyle1->GetItem(1)); + CPPUNIT_ASSERT_EQUAL(OUString("Item1"), pItem1->GetValue()); + std::shared_ptr<SfxItemSet> pStyle2 = pIter->getNext(); + CPPUNIT_ASSERT(pStyle2); + const SfxStringItem* pItem2 = static_cast<const SfxStringItem*>(pStyle2->GetItem(1)); + // Without the accompanying fix in place, this test would have failed with 'Expected: Item2; + // Actual: Item3'. The iteration order depended on the pointer address on the pointer + // address of the parents. + CPPUNIT_ASSERT_EQUAL(OUString("Item2"), pItem2->GetValue()); + std::shared_ptr<SfxItemSet> pStyle3 = pIter->getNext(); + CPPUNIT_ASSERT(pStyle3); + const SfxStringItem* pItem3 = static_cast<const SfxStringItem*>(pStyle3->GetItem(1)); + CPPUNIT_ASSERT_EQUAL(OUString("Item3"), pItem3->GetValue()); + CPPUNIT_ASSERT(!pIter->getNext()); + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/qa/unit/items/test_IndexedStyleSheets.cxx b/svl/qa/unit/items/test_IndexedStyleSheets.cxx new file mode 100644 index 0000000000..6afaca6295 --- /dev/null +++ b/svl/qa/unit/items/test_IndexedStyleSheets.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <svl/IndexedStyleSheets.hxx> + +#include <svl/style.hxx> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <algorithm> + +using namespace svl; + +namespace { + +class MockedStyleSheet : public SfxStyleSheetBase +{ + public: + MockedStyleSheet(const OUString& name, SfxStyleFamily fam = SfxStyleFamily::Char) + : SfxStyleSheetBase(name, nullptr, fam, SfxStyleSearchBits::Auto) + {} + +}; + +struct DummyPredicate : public StyleSheetPredicate { + bool Check(const SfxStyleSheetBase&) override { + return true; + } +}; + +} + +class IndexedStyleSheetsTest : public CppUnit::TestFixture +{ + void InstantiationWorks(); + void AddedStylesheetsCanBeFoundAndRetrievedByPosition(); + void AddingSameStylesheetTwiceHasNoEffect(); + void RemovedStyleSheetIsNotFound(); + void RemovingStyleSheetWhichIsNotAvailableHasNoEffect(); + void StyleSheetsCanBeRetrievedByTheirName(); + void KnowsThatItStoresAStyleSheet(); + void PositionCanBeQueriedByFamily(); + void OnlyOneStyleSheetIsReturnedWhenReturnFirstIsUsed(); + + // Adds code needed to register the test suite + CPPUNIT_TEST_SUITE(IndexedStyleSheetsTest); + + CPPUNIT_TEST(InstantiationWorks); + CPPUNIT_TEST(AddedStylesheetsCanBeFoundAndRetrievedByPosition); + CPPUNIT_TEST(AddingSameStylesheetTwiceHasNoEffect); + CPPUNIT_TEST(RemovedStyleSheetIsNotFound); + CPPUNIT_TEST(RemovingStyleSheetWhichIsNotAvailableHasNoEffect); + CPPUNIT_TEST(StyleSheetsCanBeRetrievedByTheirName); + CPPUNIT_TEST(KnowsThatItStoresAStyleSheet); + CPPUNIT_TEST(PositionCanBeQueriedByFamily); + CPPUNIT_TEST(OnlyOneStyleSheetIsReturnedWhenReturnFirstIsUsed); + + // End of test suite definition + CPPUNIT_TEST_SUITE_END(); + +}; + +void IndexedStyleSheetsTest::InstantiationWorks() +{ + IndexedStyleSheets iss; +} + +void IndexedStyleSheetsTest::AddedStylesheetsCanBeFoundAndRetrievedByPosition() +{ + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet("name1")); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet("name2")); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + unsigned pos = iss.FindStyleSheetPosition(*sheet2); + rtl::Reference<SfxStyleSheetBase> retrieved = iss.GetStyleSheetByPosition(pos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("retrieved sheet is that which has been inserted.", sheet2.get(), retrieved.get()); +} + +void IndexedStyleSheetsTest::AddingSameStylesheetTwiceHasNoEffect() +{ + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet("sheet1")); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), iss.GetNumberOfStyleSheets()); + iss.AddStyleSheet(sheet1); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), iss.GetNumberOfStyleSheets()); +} + +void IndexedStyleSheetsTest::RemovedStyleSheetIsNotFound() +{ + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet("name1")); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet("name2")); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + iss.RemoveStyleSheet(sheet1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Removed style sheet is not found.", + false, iss.HasStyleSheet(sheet1)); +} + +void IndexedStyleSheetsTest::RemovingStyleSheetWhichIsNotAvailableHasNoEffect() +{ + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet("sheet1")); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet("sheet2")); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), iss.GetNumberOfStyleSheets()); + iss.RemoveStyleSheet(sheet2); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), iss.GetNumberOfStyleSheets()); +} + +void IndexedStyleSheetsTest::StyleSheetsCanBeRetrievedByTheirName() +{ + OUString name1("name1"); + OUString name2("name2"); + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet(name1)); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet(name2)); + rtl::Reference<SfxStyleSheetBase> sheet3(new MockedStyleSheet(name1)); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + iss.AddStyleSheet(sheet3); + + std::vector<sal_Int32> r = iss.FindPositionsByName(name1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Two style sheets are found by 'name1'", + 2u, static_cast<unsigned>(r.size())); + std::sort (r.begin(), r.end()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), r.at(0)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), r.at(1)); + + r = iss.FindPositionsByName(name2); + CPPUNIT_ASSERT_EQUAL_MESSAGE("One style sheets is found by 'name2'", + 1u, static_cast<unsigned>(r.size())); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), r.at(0)); +} + +void IndexedStyleSheetsTest::KnowsThatItStoresAStyleSheet() +{ + static constexpr OUString name1(u"name1"_ustr); + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet(name1)); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet(name1)); + rtl::Reference<SfxStyleSheetBase> sheet3(new MockedStyleSheet("name2")); + rtl::Reference<SfxStyleSheetBase> sheet4(new MockedStyleSheet(name1)); + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + iss.AddStyleSheet(sheet3); + // do not add sheet 4 + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Finds first stored style sheet even though two style sheets have the same name.", + true, iss.HasStyleSheet(sheet1)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Finds second stored style sheet even though two style sheets have the same name.", + true, iss.HasStyleSheet(sheet2)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Does not find style sheet which is not stored and has the same name as a stored.", + false, iss.HasStyleSheet(sheet4)); +} + +void IndexedStyleSheetsTest::PositionCanBeQueriedByFamily() +{ + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet("name1", SfxStyleFamily::Char)); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet("name2", SfxStyleFamily::Para)); + rtl::Reference<SfxStyleSheetBase> sheet3(new MockedStyleSheet("name3", SfxStyleFamily::Char)); + + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + iss.AddStyleSheet(sheet3); + + const std::vector<sal_Int32>& v = iss.GetStyleSheetPositionsByFamily(SfxStyleFamily::Char); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Separation by family works.", static_cast<size_t>(2), v.size()); + + const std::vector<sal_Int32>& w = iss.GetStyleSheetPositionsByFamily(SfxStyleFamily::All); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wildcard works for family queries.", static_cast<size_t>(3), w.size()); +} + +void IndexedStyleSheetsTest::OnlyOneStyleSheetIsReturnedWhenReturnFirstIsUsed() +{ + OUString name("name1"); + rtl::Reference<SfxStyleSheetBase> sheet1(new MockedStyleSheet(name, SfxStyleFamily::Char)); + rtl::Reference<SfxStyleSheetBase> sheet2(new MockedStyleSheet(name, SfxStyleFamily::Para)); + rtl::Reference<SfxStyleSheetBase> sheet3(new MockedStyleSheet(name, SfxStyleFamily::Char)); + + IndexedStyleSheets iss; + iss.AddStyleSheet(sheet1); + iss.AddStyleSheet(sheet2); + iss.AddStyleSheet(sheet3); + + DummyPredicate predicate; // returns always true, i.e., all style sheets match the predicate. + + std::vector<sal_Int32> v = iss.FindPositionsByNameAndPredicate(name, predicate, + IndexedStyleSheets::SearchBehavior::ReturnFirst); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Only one style sheet is returned.", static_cast<size_t>(1), v.size()); + + std::vector<sal_Int32> w = iss.FindPositionsByNameAndPredicate(name, predicate); + CPPUNIT_ASSERT_EQUAL_MESSAGE("All style sheets are returned.", static_cast<size_t>(3), w.size()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(IndexedStyleSheetsTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/svl/qa/unit/items/test_itempool.cxx b/svl/qa/unit/items/test_itempool.cxx new file mode 100644 index 0000000000..2cb751d4fd --- /dev/null +++ b/svl/qa/unit/items/test_itempool.cxx @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <svl/itempool.hxx> +#include <svl/voiditem.hxx> +#include <poolio.hxx> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +class PoolItemTest : public CppUnit::TestFixture +{ + public: + PoolItemTest() {} + + void testPool(); + + // Adds code needed to register the test suite + CPPUNIT_TEST_SUITE(PoolItemTest); + + CPPUNIT_TEST(testPool); + + // End of test suite definition + CPPUNIT_TEST_SUITE_END(); +}; + +void PoolItemTest::testPool() +{ + SfxItemInfo const aItems[] = + { + // _nSID, _bNeedsPoolRegistration, _bShareable + { 4, true, true }, + { 3, true, false /* test NeedsPoolRegistration */ }, + { 2, false, false }, + { 1, true, false /* test NeedsPoolRegistration */} + }; + + rtl::Reference<SfxItemPool> pPool = new SfxItemPool("testpool", 1, 4, aItems); + + // Poolable + SfxVoidItem aItemOne( 1 ); + SfxVoidItem aNotherOne( 1 ); + + { + CPPUNIT_ASSERT(nullptr == pPool->ppRegisteredSfxPoolItems); + const SfxPoolItem &rVal = pPool->DirectPutItemInPool(aItemOne); + CPPUNIT_ASSERT(bool(rVal == aItemOne)); + CPPUNIT_ASSERT(nullptr != pPool->ppRegisteredSfxPoolItems); + CPPUNIT_ASSERT(nullptr != pPool->ppRegisteredSfxPoolItems[0]); + CPPUNIT_ASSERT(!pPool->ppRegisteredSfxPoolItems[0]->empty()); + const SfxPoolItem &rVal2 = pPool->DirectPutItemInPool(aNotherOne); + CPPUNIT_ASSERT(bool(rVal2 == rVal)); + + // ITEM: With leaving the paradigm that internally an already + // existing Item with true = operator==() (which is very + // expensive) the ptr's are no longer required to be equal, + // but the content-compare *is* + CPPUNIT_ASSERT(SfxPoolItem::areSame(rVal, rVal2)); + + // Clones on Put ... + // ptr compare OK, we want to check just the ptrs here + CPPUNIT_ASSERT(!areSfxPoolItemPtrsEqual(&rVal2, &aItemOne)); + CPPUNIT_ASSERT(!areSfxPoolItemPtrsEqual(&rVal2, &aNotherOne)); + CPPUNIT_ASSERT(!areSfxPoolItemPtrsEqual(&rVal, &aItemOne)); + CPPUNIT_ASSERT(!areSfxPoolItemPtrsEqual(&rVal, &aNotherOne)); + } + + // non-poolable + SfxVoidItem aItemTwo( 2 ); + SfxVoidItem aNotherTwo( 2 ); + { + CPPUNIT_ASSERT(nullptr == pPool->ppRegisteredSfxPoolItems[1]); + const SfxPoolItem &rVal = pPool->DirectPutItemInPool(aItemTwo); + CPPUNIT_ASSERT(bool(rVal == aItemTwo)); + CPPUNIT_ASSERT(nullptr != pPool->ppRegisteredSfxPoolItems[1]); + CPPUNIT_ASSERT(!pPool->ppRegisteredSfxPoolItems[1]->empty()); + const SfxPoolItem &rVal2 = pPool->DirectPutItemInPool(aNotherTwo); + CPPUNIT_ASSERT(bool(rVal2 == rVal)); + // ptr compare OK, we want to check just the ptrs here + CPPUNIT_ASSERT(!areSfxPoolItemPtrsEqual(&rVal2, &rVal)); + } + + // Test removal. + SfxVoidItem aRemoveFour(4); + SfxVoidItem aNotherFour(4); + + const SfxPoolItem &rKeyFour = pPool->DirectPutItemInPool(aRemoveFour); + pPool->DirectPutItemInPool(aNotherFour); + CPPUNIT_ASSERT(pPool->ppRegisteredSfxPoolItems[3]->size() > 0); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pPool->ppRegisteredSfxPoolItems[3]->size()); + pPool->DirectRemoveItemFromPool(rKeyFour); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pPool->ppRegisteredSfxPoolItems[3]->size()); + pPool->DirectPutItemInPool(aNotherFour); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pPool->ppRegisteredSfxPoolItems[3]->size()); +} + + +CPPUNIT_TEST_SUITE_REGISTRATION(PoolItemTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/svl/qa/unit/lockfiles/test_lockfiles.cxx b/svl/qa/unit/lockfiles/test_lockfiles.cxx new file mode 100644 index 0000000000..d66c301be4 --- /dev/null +++ b/svl/qa/unit/lockfiles/test_lockfiles.cxx @@ -0,0 +1,706 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <test/bootstrapfixture.hxx> + +#include <o3tl/cppunittraitshelper.hxx> +#include <unotest/directories.hxx> +#include <svl/lockfilecommon.hxx> +#include <svl/documentlockfile.hxx> +#include <svl/msodocumentlockfile.hxx> +#include <unotools/useroptions.hxx> +#include <tools/stream.hxx> +#include <rtl/strbuf.hxx> +#include <osl/security.hxx> +#include <osl/socket.hxx> +#include <unotools/bootstrap.hxx> + +namespace +{ +class LockfileTest : public test::BootstrapFixture +{ + OUString generateTestURL(std::u16string_view sFileName) const; + +public: + void testLOLockFileURL(); + void testLOLockFileContent(); + void testLOLockFileRT(); + void testLOLockFileUnicodeUsername(); + void testLOLockFileOverwrite(); + void testWordLockFileURL(); + void testExcelLockFileURL(); + void testPowerPointLockFileURL(); + void testWordLockFileContent(); + void testExcelLockFileContent(); + void testPowerPointLockFileContent(); + void testWordLockFileRT(); + void testExcelLockFileRT(); + void testPowerPointLockFileRT(); + void testMSOLockFileLongUserName(); + void testMSOLockFileUnicodeUsername(); + void testMSOLockFileOverwrite(); + +private: + CPPUNIT_TEST_SUITE(LockfileTest); + CPPUNIT_TEST(testLOLockFileURL); + CPPUNIT_TEST(testLOLockFileContent); + CPPUNIT_TEST(testLOLockFileRT); + CPPUNIT_TEST(testLOLockFileUnicodeUsername); + CPPUNIT_TEST(testLOLockFileOverwrite); + CPPUNIT_TEST(testWordLockFileURL); + CPPUNIT_TEST(testExcelLockFileURL); + CPPUNIT_TEST(testPowerPointLockFileURL); + CPPUNIT_TEST(testWordLockFileContent); + CPPUNIT_TEST(testExcelLockFileContent); + CPPUNIT_TEST(testPowerPointLockFileContent); + CPPUNIT_TEST(testWordLockFileRT); + CPPUNIT_TEST(testExcelLockFileRT); + CPPUNIT_TEST(testPowerPointLockFileRT); + CPPUNIT_TEST(testMSOLockFileLongUserName); + CPPUNIT_TEST(testMSOLockFileUnicodeUsername); + CPPUNIT_TEST(testMSOLockFileOverwrite); + CPPUNIT_TEST_SUITE_END(); +}; + +OUString readLockFile(const OUString& aSource) +{ + SvFileStream aFileStream(aSource, StreamMode::READ); + std::size_t nSize = aFileStream.remainingSize(); + std::unique_ptr<sal_Int8[]> pBuffer(new sal_Int8[nSize]); + aFileStream.ReadBytes(pBuffer.get(), nSize); + + const css::uno::Sequence<sal_Int8> aData(pBuffer.get(), nSize); + OStringBuffer aResult(static_cast<int>(nSize)); + for (sal_Int8 nByte : aData) + { + aResult.append(static_cast<char>(nByte)); + } + return OStringToOUString(aResult.makeStringAndClear(), RTL_TEXTENCODING_UTF8); +} + +OUString GetLockFileName(const svt::GenDocumentLockFile& rLockFile) +{ + INetURLObject aDocURL = svt::LockFileCommon::ResolveLinks(INetURLObject(rLockFile.GetURL())); + return aDocURL.GetLastName(); +} + +OUString LockfileTest::generateTestURL(std::u16string_view sFileName) const +{ + return m_directories.getURLFromWorkdir(u"/CppunitTest/svl_lockfiles.test.user/") + sFileName; +} + +void LockfileTest::testLOLockFileURL() +{ + // Test the generated file name for LibreOffice lock files + OUString aTestODT = generateTestURL(u"testLOLockFileURL.odt"); + + svt::DocumentLockFile aLockFile(aTestODT); + CPPUNIT_ASSERT_EQUAL(OUString(".~lock.testLOLockFileURL.odt%23"), GetLockFileName(aLockFile)); +} + +void LockfileTest::testLOLockFileContent() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = generateTestURL(u"testLOLockFileContent.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::DocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // User name + sal_Int32 nFirstChar = 0; + sal_Int32 nNextComma = sLockFileContent.indexOf(',', nFirstChar); + OUString sUserName = aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName(); + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // System user name + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + ::osl::Security aSecurity; + OUString sSysUserName; + aSecurity.getUserName(sSysUserName); + CPPUNIT_ASSERT_EQUAL(sSysUserName, sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // Local host + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + CPPUNIT_ASSERT_EQUAL(::osl::SocketAddr::getLocalHostname(), + sLockFileContent.copy(nFirstChar, nNextComma - nFirstChar)); + + // Skip date and time because it changes after the lock file was created + nFirstChar = nNextComma + 1; + nNextComma = sLockFileContent.indexOf(',', nFirstChar); + + // user url + nFirstChar = nNextComma + 1; + OUString aUserInstDir; + ::utl::Bootstrap::locateUserInstallation(aUserInstDir); + CPPUNIT_ASSERT_EQUAL( + aUserInstDir, + sLockFileContent.copy(nFirstChar, sLockFileContent.getLength() - nFirstChar - 1)); +} + +void LockfileTest::testLOLockFileRT() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = generateTestURL(u"testLOLockFileRT.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::SYSUSERNAME], + aRTEntry[LockFileComponent::SYSUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::LOCALHOST], + aRTEntry[LockFileComponent::LOCALHOST]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::USERURL], + aRTEntry[LockFileComponent::USERURL]); + // LockFileComponent::EDITTIME can change + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testLOLockFileUnicodeUsername() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = generateTestURL(u"testLOLockFileUnicodeUsername.odt"); + + // Set user name + SvtUserOptions aUserOpt; + sal_Unicode vFirstName[] = { 2351, 2676, 3117, 5279 }; + aUserOpt.SetToken(UserOptToken::FirstName, OUString(vFirstName, 4)); + sal_Unicode vLastName[] = { 671, 1245, 1422, 1822 }; + aUserOpt.SetToken(UserOptToken::LastName, OUString(vLastName, 4)); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(OUString(aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName()), + aOrigEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testLOLockFileOverwrite() +{ + OUString aTestODT = generateTestURL(u"testLOLockFileOverwrite.odt"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + + // Change user name + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile2"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Overwrite lockfile + svt::DocumentLockFile aLockFile2(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile2.OverwriteOwnLockFile(); + + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::SYSUSERNAME], + aRTEntry[LockFileComponent::SYSUSERNAME]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::LOCALHOST], + aRTEntry[LockFileComponent::LOCALHOST]); + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::USERURL], + aRTEntry[LockFileComponent::USERURL]); + + aLockFile2.RemoveFileDirectly(); +} + +void LockfileTest::testWordLockFileURL() +{ + // Test the generated file name for Word lock files + + // Word specific file format + { + OUString aTestFile = generateTestURL(u"testWordLockFileURL.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$stWordLockFileURL.docx"), GetLockFileName(aLockFile)); + } + + // Eight character file name (cuts two characters) + { + OUString aTestFile = generateTestURL(u"12345678.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.docx"), GetLockFileName(aLockFile)); + } + + // Seven character file name (cuts one character) + { + OUString aTestFile = generateTestURL(u"1234567.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$234567.docx"), GetLockFileName(aLockFile)); + } + + // Six character file name (cuts no character) + { + OUString aTestFile = generateTestURL(u"123456.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$123456.docx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = generateTestURL(u"1.docx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.docx"), GetLockFileName(aLockFile)); + } + + // Test for ODT format + { + OUString aTestFile = generateTestURL(u"12345678.odt"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.odt"), GetLockFileName(aLockFile)); + } + + // Test for DOC format + { + OUString aTestFile = generateTestURL(u"12345678.doc"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.doc"), GetLockFileName(aLockFile)); + } + + // Test for RTF format + { + OUString aTestFile = generateTestURL(u"12345678.rtf"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$345678.rtf"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testExcelLockFileURL() +{ + // Test the generated file name for Excel lock files + { + OUString aTestFile = generateTestURL(u"testExcelLockFileURL.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$testExcelLockFileURL.xlsx"), GetLockFileName(aLockFile)); + } + + // Eight character file name + { + OUString aTestFile = generateTestURL(u"12345678.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.xlsx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = generateTestURL(u"1.xlsx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.xlsx"), GetLockFileName(aLockFile)); + } + + // Test for ODS format + { + OUString aTestFile = generateTestURL(u"12345678.ods"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.ods"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testPowerPointLockFileURL() +{ + // Test the generated file name for PowerPoint lock files + { + OUString aTestFile = generateTestURL(u"testPowerPointLockFileURL.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$testPowerPointLockFileURL.pptx"), + GetLockFileName(aLockFile)); + } + + // Eight character file name + { + OUString aTestFile = generateTestURL(u"12345678.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.pptx"), GetLockFileName(aLockFile)); + } + + // One character file name + { + OUString aTestFile = generateTestURL(u"1.pptx"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$1.pptx"), GetLockFileName(aLockFile)); + } + + // Test for PPT format + { + OUString aTestFile = generateTestURL(u"12345678.ppt"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.ppt"), GetLockFileName(aLockFile)); + } + + // Test for ODP format + { + OUString aTestFile = generateTestURL(u"/12345678.odp"); + svt::MSODocumentLockFile aLockFile(aTestFile); + CPPUNIT_ASSERT_EQUAL(OUString("~$12345678.odp"), GetLockFileName(aLockFile)); + } +} + +void LockfileTest::testWordLockFileContent() +{ + // Test the lockfile generated for the specified DOCX document + OUString aTestFile = generateTestURL(u"testWordLockFileContent.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName = aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling 0 bytes after the user name + for (nIndex = sUserName.getLength() + 1; nIndex < MSO_USERNAME_MAX_LENGTH + 2; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[55 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[55 + i * 2 + 1]))); + } + + // We have some filling 0 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 1; nIndex < MSO_WORD_LOCKFILE_SIZE; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_WORD_LOCKFILE_SIZE), sLockFileContent.getLength()); +} + +void LockfileTest::testExcelLockFileContent() +{ + // Test the lockfile generated for the specified XLSX document + OUString aTestFile = generateTestURL(u"testExcelLockFileContent.xlsx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName = aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling 0x20 bytes after the user name + for (nIndex = sUserName.getLength() + 1; nIndex < MSO_USERNAME_MAX_LENGTH + 3; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[56 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[56 + i * 2 + 1]))); + } + + // We have some filling 0 and 0x20 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 2; nIndex < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE; + nIndex += 2) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + if (nIndex + 1 < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE) + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), + static_cast<sal_Int32>(sLockFileContent[nIndex + 1])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE), + sLockFileContent.getLength()); +} + +void LockfileTest::testPowerPointLockFileContent() +{ + // Test the lockfile generated for the specified PPTX document + OUString aTestFile = generateTestURL(u"testPowerPointLockFileContent.pptx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and check the content + svt::MSODocumentLockFile aLockFile(aTestFile); + aLockFile.CreateOwnLockFile(); + OUString sLockFileContent(readLockFile(aLockFile.GetURL())); + aLockFile.RemoveFileDirectly(); + + // First character is the size of the user name + OUString sUserName = aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName(); + int nIndex = 0; + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name + CPPUNIT_ASSERT_EQUAL(sUserName, sLockFileContent.copy(1, sUserName.getLength())); + + // We have some filling bytes after the user name + nIndex = sUserName.getLength() + 1; + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(sLockFileContent[nIndex])); + for (nIndex += 1; nIndex < MSO_USERNAME_MAX_LENGTH + 3; ++nIndex) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + } + + // Then we have the user name's length again + CPPUNIT_ASSERT_EQUAL(sUserName.getLength(), static_cast<sal_Int32>(sLockFileContent[nIndex])); + + // Then we have the user name again with 16 bit coding + for (int i = 0; i < sUserName.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL( + sUserName[i], + static_cast<sal_Unicode>(static_cast<sal_Int16>(sLockFileContent[56 + i * 2]) + + static_cast<sal_Int16>(sLockFileContent[56 + i * 2 + 1]))); + } + + // We have some filling 0 and 0x20 bytes after the user name + for (nIndex += sUserName.getLength() * 2 + 2; nIndex < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE; + nIndex += 2) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(0x20), static_cast<sal_Int32>(sLockFileContent[nIndex])); + if (nIndex + 1 < MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE) + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), + static_cast<sal_Int32>(sLockFileContent[nIndex + 1])); + } + + // We have a fixed size lock file + CPPUNIT_ASSERT_EQUAL(sal_Int32(MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE), + sLockFileContent.getLength()); +} + +void LockfileTest::testWordLockFileRT() +{ + OUString aTestODT = generateTestURL(u"testWordLockFileRT.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testExcelLockFileRT() +{ + OUString aTestODT = generateTestURL(u"testExcelLockFileRT.xlsx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testPowerPointLockFileRT() +{ + OUString aTestODT = generateTestURL(u"testPowerPointLockFileRT.pptx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + aLockFile.RemoveFileDirectly(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); +} + +void LockfileTest::testMSOLockFileLongUserName() +{ + OUString aTestODT = generateTestURL(u"testMSOLockFileLongUserName.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + aUserOpt.SetToken(UserOptToken::LastName, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the user name was cut to the maximum length + CPPUNIT_ASSERT_EQUAL( + aOrigEntry[LockFileComponent::OOOUSERNAME].copy(0, MSO_USERNAME_MAX_LENGTH), + aRTEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testMSOLockFileUnicodeUsername() +{ + // Test the lockfile generated for the specified ODT document + OUString aTestODT = generateTestURL(u"testMSOLockFileUnicodeUsername.docx"); + + // Set user name + SvtUserOptions aUserOpt; + sal_Unicode vFirstName[] = { 2351, 2676, 3117, 5279 }; + aUserOpt.SetToken(UserOptToken::FirstName, OUString(vFirstName, 4)); + sal_Unicode vLastName[] = { 671, 1245, 1422, 1822 }; + aUserOpt.SetToken(UserOptToken::LastName, OUString(vLastName, 4)); + + // Write the lock file and read it back + svt::DocumentLockFile aLockFile(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile.CreateOwnLockFile(); + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the user name is the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + CPPUNIT_ASSERT_EQUAL(OUString(aUserOpt.GetFirstName() + " " + aUserOpt.GetLastName()), + aOrigEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile.RemoveFileDirectly(); +} + +void LockfileTest::testMSOLockFileOverwrite() +{ + OUString aTestODT = generateTestURL(u"testMSOLockFileOverwrite.docx"); + + // Set user name + SvtUserOptions aUserOpt; + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Write the lock file and read it back + svt::MSODocumentLockFile aLockFile(aTestODT); + aLockFile.CreateOwnLockFile(); + + // Change user name + aUserOpt.SetToken(UserOptToken::FirstName, "LockFile2"); + aUserOpt.SetToken(UserOptToken::LastName, "Test"); + + // Overwrite lockfile + svt::MSODocumentLockFile aLockFile2(aTestODT); + LockFileEntry aOrigEntry = svt::LockFileCommon::GenerateOwnEntry(); + aLockFile2.OverwriteOwnLockFile(); + + LockFileEntry aRTEntry = aLockFile.GetLockData(); + + // Check whether the lock file attributes are the same + CPPUNIT_ASSERT_EQUAL(aOrigEntry[LockFileComponent::OOOUSERNAME], + aRTEntry[LockFileComponent::OOOUSERNAME]); + + aLockFile2.RemoveFileDirectly(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(LockfileTest); +} // namespace + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svl/qa/unit/notify/test_SfxBroadcaster.cxx b/svl/qa/unit/notify/test_SfxBroadcaster.cxx new file mode 100644 index 0000000000..ffed864ff6 --- /dev/null +++ b/svl/qa/unit/notify/test_SfxBroadcaster.cxx @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <svl/SfxBroadcaster.hxx> + +#include <svl/lstner.hxx> +#include <svl/hint.hxx> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +class SfxBroadcasterTest : public CppUnit::TestFixture +{ + void AddingListenersIncreasesCount(); + void RemovingListenersDecreasesCount(); + void HintsAreNotForwardedToRemovedListeners(); + void SameListenerCanBeAddedMoreThanOnce(); + void StoppingListeningAffectsOnlyFirstOfIdenticalListeners(); + + // Adds code needed to register the test suite + CPPUNIT_TEST_SUITE(SfxBroadcasterTest); + CPPUNIT_TEST(AddingListenersIncreasesCount); + CPPUNIT_TEST(RemovingListenersDecreasesCount); + CPPUNIT_TEST(HintsAreNotForwardedToRemovedListeners); + CPPUNIT_TEST(SameListenerCanBeAddedMoreThanOnce); + CPPUNIT_TEST(StoppingListeningAffectsOnlyFirstOfIdenticalListeners); + + CPPUNIT_TEST_SUITE_END(); +}; + +namespace +{ +class MockedSfxListener : public SfxListener +{ +public: + MockedSfxListener() + : mNotifyWasCalled(false) + { + } + + void Notify(SfxBroadcaster&, const SfxHint&) override { mNotifyWasCalled = true; } + + bool NotifyWasCalled() const { return mNotifyWasCalled; } + +private: + bool mNotifyWasCalled; +}; +} + +void SfxBroadcasterTest::AddingListenersIncreasesCount() +{ + SfxBroadcaster sb; + MockedSfxListener sl; + + CPPUNIT_ASSERT_EQUAL(size_t(0), sb.GetListenerCount()); + + sl.StartListening(sb, DuplicateHandling::Prevent); + CPPUNIT_ASSERT_EQUAL(size_t(1), sb.GetListenerCount()); +} + +void SfxBroadcasterTest::RemovingListenersDecreasesCount() +{ + SfxBroadcaster sb; + MockedSfxListener sl; + + CPPUNIT_ASSERT_EQUAL(size_t(0), sb.GetListenerCount()); + sl.StartListening(sb, DuplicateHandling::Prevent); + CPPUNIT_ASSERT_EQUAL(size_t(1), sb.GetListenerCount()); + sl.EndListening(sb, true); + CPPUNIT_ASSERT_EQUAL(size_t(0), sb.GetListenerCount()); +} + +void SfxBroadcasterTest::HintsAreNotForwardedToRemovedListeners() +{ + SfxBroadcaster sb; + MockedSfxListener sl1; + MockedSfxListener sl2; + SfxHint hint; + + sl1.StartListening(sb, DuplicateHandling::Prevent); + sl2.StartListening(sb, DuplicateHandling::Prevent); + CPPUNIT_ASSERT_EQUAL_MESSAGE("All listeners were added.", size_t(2), sb.GetListenerCount()); + sl1.EndListening(sb, true); + sb.Forward(sb, hint); + CPPUNIT_ASSERT_EQUAL(true, sl2.NotifyWasCalled()); + CPPUNIT_ASSERT_EQUAL(false, sl1.NotifyWasCalled()); +} + +void SfxBroadcasterTest::SameListenerCanBeAddedMoreThanOnce() +{ + MockedSfxListener sl; + SfxBroadcaster sb; + sb.AddListener(sl); + sb.AddListener(sl); + CPPUNIT_ASSERT_EQUAL(size_t(2), sb.GetListenerCount()); +} + +void SfxBroadcasterTest::StoppingListeningAffectsOnlyFirstOfIdenticalListeners() +{ + MockedSfxListener sl; + SfxBroadcaster sb; + sb.AddListener(sl); + sb.AddListener(sl); + sb.RemoveListener(sl); + CPPUNIT_ASSERT_EQUAL(size_t(1), sb.GetListenerCount()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(SfxBroadcasterTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/svl/qa/unit/svl.cxx b/svl/qa/unit/svl.cxx new file mode 100644 index 0000000000..4fa56f4bcc --- /dev/null +++ b/svl/qa/unit/svl.cxx @@ -0,0 +1,2002 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <sal/config.h> + +#include <cppuhelper/bootstrap.hxx> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <i18nlangtag/lang.h> + +#include <math.h> + +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <svl/sharedstringpool.hxx> +#include <svl/sharedstring.hxx> +#include <tools/color.hxx> +#include <unotools/syslocale.hxx> + +#include <memory> +#include <optional> +#include <unicode/timezone.h> + +using namespace ::com::sun::star; +using namespace svl; + +namespace svl +{ +static std::ostream& operator<<(std::ostream& rStrm, const SharedString& string ) +{ + return rStrm << "(" << static_cast<const void*>(string.getData()) << ")" << string.getString(); +} +} + + +namespace { + +class Test : public CppUnit::TestFixture { +public: + Test(); + virtual ~Test() override; + + virtual void tearDown() override; + + void testNumberFormat(); + void testSharedString(); + void testSharedStringPool(); + void testSharedStringPoolPurge(); + void testSharedStringPoolPurgeBug1(); + void testSharedStringPoolEmptyString(); + void testFdo60915(); + void testI116701(); + void testTdf103060(); + void testDateInput(); + void testIsNumberFormat(); + void testIsNumberFormatSpecific(); + void testUserDefinedNumberFormats(); + void testNfEnglishKeywordsIntegrity(); + void testStandardColorIntegrity(); + void testColorNamesConversion(); + void testExcelExportFormats(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testNumberFormat); + CPPUNIT_TEST(testSharedString); + CPPUNIT_TEST(testSharedStringPool); + CPPUNIT_TEST(testSharedStringPoolPurge); + CPPUNIT_TEST(testSharedStringPoolPurgeBug1); + CPPUNIT_TEST(testSharedStringPoolEmptyString); + CPPUNIT_TEST(testFdo60915); + CPPUNIT_TEST(testI116701); + CPPUNIT_TEST(testTdf103060); + CPPUNIT_TEST(testDateInput); + CPPUNIT_TEST(testIsNumberFormat); + CPPUNIT_TEST(testIsNumberFormatSpecific); + CPPUNIT_TEST(testUserDefinedNumberFormats); + CPPUNIT_TEST(testNfEnglishKeywordsIntegrity); + CPPUNIT_TEST(testStandardColorIntegrity); + CPPUNIT_TEST(testColorNamesConversion); + CPPUNIT_TEST(testExcelExportFormats); + CPPUNIT_TEST_SUITE_END(); + +protected: + uno::Reference< uno::XComponentContext > m_xContext; + void checkPreviewString(SvNumberFormatter& aFormatter, + const OUString& sCode, + double fPreviewNumber, + LanguageType eLang, + OUString const & sExpected); + void checkDateInput( SvNumberFormatter& rFormatter, const char* pTimezone, const char* pIsoDate ); + std::unique_ptr<icu::TimeZone> m_pDefaultTimeZone; +}; + +Test::Test() : m_xContext(cppu::defaultBootstrap_InitialComponentContext()) +{ + uno::Reference<lang::XMultiComponentFactory> xFactory(m_xContext->getServiceManager()); + uno::Reference<lang::XMultiServiceFactory> xSM(xFactory, uno::UNO_QUERY_THROW); + + //Without this we're crashing because callees are using + //getProcessServiceFactory. In general those should be removed in favour + //of retaining references to the root ServiceFactory as it's passed around + comphelper::setProcessServiceFactory(xSM); + m_pDefaultTimeZone.reset(icu::TimeZone::createDefault()); +} + +void Test::tearDown() +{ + icu::TimeZone::setDefault(*m_pDefaultTimeZone); +} + +Test::~Test() +{ + uno::Reference< lang::XComponent >(m_xContext, uno::UNO_QUERY_THROW)->dispose(); +} + +void Test::testNumberFormat() +{ + LanguageType eLang = LANGUAGE_ENGLISH_US; + + const char* pNumber[] = { + "General", + "0", + "0.00", + "#,##0", + "#,##0.00", + "#,###.00", + nullptr + }; + + const char* pScientific[] = { + "0.00E+000", + "0.00E+00", + nullptr + }; + + const char* pPercent[] = { + "0%", + "0.00%", + nullptr + }; + + const char* pFraction[] = { + "# \?/\?", + "# \?\?/\?\?", + nullptr + }; + +// Following aren't in range of NF_FRACTION_START and NF_FRACTION_END +// see enum NfIndexTableOffset in svl/inc/svl/zforlist.hxx + const char* pFractionExt[] = { + "# \?\?\?/\?\?\?", + "# \?/2", + "# \?/4", + "# \?/8", + "# \?\?/16", + "# \?\?/10", + "# \?\?/100", + nullptr + }; + + const char* pCurrency[] = { + "$#,##0;-$#,##0", + "$#,##0.00;-$#,##0.00", + "$#,##0;[RED]-$#,##0", + "$#,##0.00;[RED]-$#,##0.00", + "#,##0.00 CCC", + "$#,##0.--;[RED]-$#,##0.--", + nullptr + }; + + const char* pDate[] = { + "M/D/YY", + "NNNNMMMM D, YYYY", + "MM/DD/YY", + "MM/DD/YYYY", + "MMM D, YY", + "MMM D, YYYY", + "D. MMM. YYYY", + "MMMM D, YYYY", + "D. MMMM YYYY", + "NN, MMM D, YY", + "NN DD/MMM YY", + "NN, MMMM D, YYYY", + "NNNNMMMM D, YYYY", + "MM-DD", + "YY-MM-DD", + "YYYY-MM-DD", + "MM/YY", + "MMM DD", + "MMMM", + "QQ YY", + "WW", + nullptr + }; + + const char* pTime[] = { + "HH:MM", + "HH:MM:SS", + "HH:MM AM/PM", + "HH:MM:SS AM/PM", + "[HH]:MM:SS", + "MM:SS.00", + "[HH]:MM:SS.00", + nullptr + }; + + const char* pDateTime[] = { + "MM/DD/YY HH:MM AM/PM", + "MM/DD/YYYY HH:MM:SS", + nullptr + }; + +// Following aren't in range of NF_DATETIME_START and NF_DATETIME_END +// see enum NfIndexTableOffset in svl/inc/svl/zforlist.hxx + const char* pDateTimeExt1[] = { + "MM/DD/YYYY HH:MM AM/PM", + nullptr + }; + + const char* pDateTimeExt2[] = { + "YYYY-MM-DD HH:MM:SS", + "YYYY-MM-DD HH:MM:SS.000", + "YYYY-MM-DD\"T\"HH:MM:SS", + "YYYY-MM-DD\"T\"HH:MM:SS.000", + nullptr + }; + + const char* pBoolean[] = { + "BOOLEAN", + nullptr + }; + + const char* pText[] = { + "@", + nullptr + }; + + struct { + NfIndexTableOffset eStart; + NfIndexTableOffset eEnd; + size_t nSize; + const char** pCodes; + } aTests[] = { + { NF_NUMBER_START, NF_NUMBER_END, 6, pNumber }, + { NF_SCIENTIFIC_START, NF_SCIENTIFIC_END, 2, pScientific }, + { NF_PERCENT_START, NF_PERCENT_END, 2, pPercent }, + { NF_FRACTION_START, NF_FRACTION_END, 2, pFraction }, + { NF_CURRENCY_START, NF_CURRENCY_END, 6, pCurrency }, + { NF_DATE_START, NF_DATE_END, 21, pDate }, + { NF_TIME_START, NF_TIME_END, 7, pTime }, + { NF_DATETIME_START, NF_DATETIME_END, 2, pDateTime }, + { NF_BOOLEAN, NF_BOOLEAN, 1, pBoolean }, + { NF_TEXT, NF_TEXT, 1, pText }, + { NF_DATETIME_SYS_DDMMYYYY_HHMM, NF_DATETIME_SYS_DDMMYYYY_HHMM, 1, pDateTimeExt1 }, + { NF_FRACTION_3D, NF_FRACTION_100, 7, pFractionExt }, + { NF_DATETIME_ISO_YYYYMMDD_HHMMSS, NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, 4, pDateTimeExt2 } + }; + + SvNumberFormatter aFormatter(m_xContext, eLang); + + for (auto const[eStart, eEnd, nSize, pCodes] : aTests) + { + size_t nStart = eStart; + size_t nEnd = eEnd; + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected number of formats for this category.", nSize, + (nEnd - nStart + 1)); + + for (size_t j = nStart; j <= nEnd; ++j) + { + sal_uInt32 nIndex = + aFormatter.GetFormatIndex(static_cast<NfIndexTableOffset>(j)); + const SvNumberformat* p = aFormatter.GetEntry(nIndex); + + CPPUNIT_ASSERT_MESSAGE("Number format entry is expected, but doesn't exist.", p); + OUString aCode = p->GetFormatstring(); + CPPUNIT_ASSERT_EQUAL(OString( pCodes[j-nStart]), aCode.toUtf8()); + } + } + + sal_Int32 nPos; + SvNumFormatType nType = SvNumFormatType::DEFINED; + sal_uInt32 nKey; + OUString aCode; + // Thai date format (implicit locale). + aCode = "[$-1070000]d/mm/yyyy;@"; + if (!aFormatter.PutEntry(aCode, nPos, nType, nKey)) + { + CPPUNIT_ASSERT_MESSAGE("failed to insert format code '[$-1070000]d/mm/yyyy;@'", false); + } + + // Thai date format (explicit locale) + aCode = "[$-107041E]d/mm/yyyy;@"; + if (!aFormatter.PutEntry(aCode, nPos, nType, nKey)) + { + CPPUNIT_ASSERT_MESSAGE("failed to insert format code '[$-107041E]d/mm/yyyy;@'", false); + } + + // Thai date format (using buddhist calendar type). + aCode = "[~buddhist]D MMMM YYYY"; + if (!aFormatter.PutEntry(aCode, nPos, nType, nKey)) + { + CPPUNIT_ASSERT_MESSAGE("failed to insert format code '[~buddhist]D MMMM YYYY'", false); + } +} + +void Test::testSharedString() +{ + // Use shared string as normal, non-shared string, which is allowed. + SharedString aSS1("Test"), aSS2("Test"); + CPPUNIT_ASSERT_MESSAGE("Equality check should return true.", bool(aSS1 == aSS2)); + SharedString aSS3("test"); + CPPUNIT_ASSERT_MESSAGE("Equality check is case sensitive.", aSS1 != aSS3); +} + +void Test::testSharedStringPool() +{ + SvtSysLocale aSysLocale; + svl::SharedStringPool aPool(aSysLocale.GetCharClass()); + + svl::SharedString p1, p2; + p1 = aPool.intern("Andy"); + p2 = aPool.intern("Andy"); + CPPUNIT_ASSERT_EQUAL(p1.getData(), p2.getData()); + + p2 = aPool.intern("Bruce"); + CPPUNIT_ASSERT_MESSAGE("They must differ.", p1.getData() != p2.getData()); + + OUString aAndy("Andy"); + p1 = aPool.intern("Andy"); + p2 = aPool.intern(aAndy); + CPPUNIT_ASSERT_MESSAGE("Identifier shouldn't be NULL.", p1.getData()); + CPPUNIT_ASSERT_MESSAGE("Identifier shouldn't be NULL.", p2.getData()); + CPPUNIT_ASSERT_EQUAL(p1.getData(), p2.getData()); + + // Test case insensitive string ID's. + p1 = aPool.intern(aAndy); + p2 = aPool.intern("andy"); + CPPUNIT_ASSERT_MESSAGE("Failed to intern strings.", p1.getData()); + CPPUNIT_ASSERT_MESSAGE("Failed to intern strings.", p2.getData()); + CPPUNIT_ASSERT_MESSAGE("These two ID's should differ.", p1.getData() != p2.getData()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("These two ID's should be equal.", p2.getDataIgnoreCase(), p1.getDataIgnoreCase()); + p2 = aPool.intern("ANDY"); + CPPUNIT_ASSERT_MESSAGE("Failed to intern string.", p2.getData()); + CPPUNIT_ASSERT_MESSAGE("These two ID's should differ.", p1.getData() != p2.getData()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("These two ID's should be equal.", p2.getDataIgnoreCase(), p1.getDataIgnoreCase()); +} + +void Test::testSharedStringPoolPurge() +{ + SvtSysLocale aSysLocale; + svl::SharedStringPool aPool(aSysLocale.GetCharClass()); + size_t extraCount = aPool.getCount(); // internal items such as SharedString::getEmptyString() + size_t extraCountIgnoreCase = aPool.getCountIgnoreCase(); + + aPool.intern("Andy"); + aPool.intern("andy"); + aPool.intern("ANDY"); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong string count.", 3+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong case insensitive string count.", 1+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Since no string objects referencing the pooled strings exist, purging + // the pool should empty it (except for internal items). + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Now, create string objects using optional so we can clear them + std::optional<svl::SharedString> pStr1 = aPool.intern("Andy"); + std::optional<svl::SharedString> pStr2 = aPool.intern("andy"); + std::optional<svl::SharedString> pStr3 = aPool.intern("ANDY"); + std::optional<svl::SharedString> pStr4 = aPool.intern("Bruce"); + + CPPUNIT_ASSERT_EQUAL(5+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // This shouldn't purge anything. + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(5+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Delete one heap string object, and purge. That should purge one string. + pStr1.reset(); + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(4+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Nothing changes, because the upper-string is still in the map + pStr3.reset(); + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(4+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Again. + pStr2.reset(); + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(2+extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(1+extraCountIgnoreCase, aPool.getCountIgnoreCase()); + + // Delete 'Bruce' and purge. + pStr4.reset(); + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(extraCountIgnoreCase, aPool.getCountIgnoreCase()); +} + +void Test::testSharedStringPoolPurgeBug1() +{ + // We had a bug where, if we had two strings that mapped to the same uppercase string, + // purge() would de-reference a dangling pointer and consequently cause an ASAN failure. + SvtSysLocale aSysLocale; + svl::SharedStringPool aPool(aSysLocale.GetCharClass()); + size_t extraCount = aPool.getCount(); // internal items such as SharedString::getEmptyString() + size_t extraCountIgnoreCase = aPool.getCountIgnoreCase(); + aPool.intern("Andy"); + aPool.intern("andy"); + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(extraCount, aPool.getCount()); + CPPUNIT_ASSERT_EQUAL(extraCountIgnoreCase, aPool.getCountIgnoreCase()); +} + +void Test::testSharedStringPoolEmptyString() +{ + // Make sure SharedString::getEmptyString() is in the pool and matches empty strings. + SvtSysLocale aSysLocale; + svl::SharedStringPool aPool(aSysLocale.GetCharClass()); + aPool.intern(""); + CPPUNIT_ASSERT_EQUAL(SharedString::getEmptyString(), aPool.intern("")); + CPPUNIT_ASSERT_EQUAL(SharedString::getEmptyString(), aPool.intern(SharedString::EMPTY_STRING)); + // And it should still work even after purging. + aPool.purge(); + CPPUNIT_ASSERT_EQUAL(SharedString::getEmptyString(), aPool.intern(SharedString::EMPTY_STRING)); +} + +void Test::checkPreviewString(SvNumberFormatter& aFormatter, + const OUString& sCode, + double fPreviewNumber, + LanguageType eLang, + OUString const & sExpected) +{ + OUString sStr; + const Color* pColor = nullptr; + if (!aFormatter.GetPreviewString(sCode, fPreviewNumber, sStr, &pColor, eLang)) + { + OString aMessage = "GetPreviewString( \"" + + OUStringToOString( sCode, RTL_TEXTENCODING_ASCII_US ) + + "\", " + + OString::number( fPreviewNumber ) + + ", sStr, ppColor, "; + aMessage += OString::number( static_cast<sal_uInt16>(eLang) ) + + " ) failed"; + CPPUNIT_FAIL( aMessage.getStr() ); + } + CPPUNIT_ASSERT_EQUAL(sExpected, sStr); +} + +void Test::testFdo60915() +{ + LanguageType eLang = LANGUAGE_THAI; + OUString sCode, sExpected; + double fPreviewNumber = 36486; // equals 1999-11-22 (2542 B.E.) + SvNumberFormatter aFormatter(m_xContext, eLang); + { + sCode = "[~buddhist]D/MM/YYYY"; + sExpected = "22/11/2542"; + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + } + { + sCode = "[~buddhist]D/MM/YY"; + sExpected = "22/11/42"; + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + } + { + sCode = "[NatNum1][$-41E][~buddhist]D/MM/YYYY"; + sal_Unicode sTemp[] = + { + 0x0E52, 0x0E52, 0x002F, + 0x0E51, 0x0E51, 0x002F, + 0x0E52, 0x0E55, 0x0E54, 0x0E52 + }; + sExpected = OUString(sTemp, SAL_N_ELEMENTS(sTemp)); + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + } + { + sCode = "[NatNum1][$-41E][~buddhist]D/MM/YY"; + sal_Unicode sTemp[] = + { + 0x0E52, 0x0E52, 0x002F, + 0x0E51, 0x0E51, 0x002F, + 0x0E54, 0x0E52 + }; + sExpected = OUString(sTemp, SAL_N_ELEMENTS(sTemp)); + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + } +} + +// https://bz.apache.org/ooo/show_bug.cgi?id=116701 +void Test::testI116701() +{ + LanguageType eLang = LANGUAGE_CHINESE_TRADITIONAL; + OUString sCode, sExpected; + double fPreviewNumber = 40573; // equals 30/01/2011 + SvNumberFormatter aFormatter(m_xContext, eLang); + // DateFormatskey25 in i18npool/source/localedata/data/zh_TW.xml + sal_Unicode CODE1[] = + { + 0x0047, 0x0047, 0x0047, 0x0045, 0x0045, // GGGEE + 0x0022, 0x5E74, 0x0022, + 0x004D, // M + 0x0022, 0x6708, 0x0022, + 0x0044, // D + 0x0022, 0x65E5, 0x0022 + }; + sCode = OUString(CODE1, SAL_N_ELEMENTS(CODE1)); + sal_Unicode EXPECTED[] = + { + 0x4E2D, 0x83EF, 0x6C11, 0x570B, + 0x0031, 0x0030, 0x0030, // 100 + 0x5E74, + 0x0031, // 1 + 0x6708, + 0x0033, 0x0030, // 30 + 0x65E5 + }; + sExpected = OUString(EXPECTED, SAL_N_ELEMENTS(EXPECTED)); + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + sal_Unicode CODE2[] = + { + 0x0047, 0x0047, 0x0047, 0x0045, // GGGE + 0x0022, 0x5E74, 0x0022, + 0x004D, // M + 0x0022, 0x6708, 0x0022, + 0x0044, // D + 0x0022, 0x65E5, 0x0022 + }; + sCode = OUString(CODE2, SAL_N_ELEMENTS(CODE2)); + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); +} + +void Test::testTdf103060() +{ + LanguageType eLang = LANGUAGE_JAPANESE; + OUString sCode, sExpected; + double fPreviewNumber = 42655; // equals 2016-10-12 + SvNumberFormatter aFormatter(m_xContext, eLang); + sCode = "G"; + sExpected = "H"; // Heisei era + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + sCode = "GG"; + sExpected = u"\u5E73"_ustr; + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); + sCode = "GGG"; + sExpected = u"\u5E73\u6210"_ustr; + checkPreviewString(aFormatter, sCode, fPreviewNumber, eLang, sExpected); +} + +void Test::testDateInput() +{ + const char* aData[][2] = { + { "Europe/Paris", "1938-10-07" }, // i#76623 + { "Europe/Moscow", "1919-07-01" }, // i#86094 + { "America/St_Johns", "1935-03-30" }, // i#86094 i#90627 + { "Europe/Tallinn", "1790-03-01" }, // i#105864 + { "Australia/Perth", "2004-04-11" }, // i#17222 + { "America/Sao_Paulo", "1902-04-22" }, // tdf#44286 + { "Europe/Berlin", "1790-07-27" }, + { "US/Mountain", "1790-07-26" }, + { "Asia/Tehran", "1999-03-22" }, + + // Data from https://bugs.documentfoundation.org/show_bug.cgi?id=63230 + // https://bugs.documentfoundation.org/attachment.cgi?id=79051 + // https://bugs.documentfoundation.org/show_bug.cgi?id=79663 + { "Africa/Accra", "1800-01-01" }, + { "Africa/Accra", "1800-04-10" }, + { "Africa/Addis_Ababa", "1870-01-01" }, + { "Africa/Addis_Ababa", "1936-05-05" }, + { "Africa/Algiers", "1956-01-29" }, + { "Africa/Algiers", "1981-05-01" }, + { "Africa/Asmara", "1936-05-05" }, + { "Africa/Asmera", "1936-05-05" }, + { "Africa/Bujumbura", "1890-01-01" }, + { "Africa/Casablanca", "1984-03-16" }, + { "Africa/Ceuta", "1984-03-16" }, + { "Africa/Dar_es_Salaam", "1931-01-01" }, + { "Africa/Dar_es_Salaam", "1961-01-01" }, + { "Africa/Djibouti", "1911-07-01" }, + { "Africa/Douala", "1912-01-01" }, + { "Africa/El_Aaiun", "1934-01-01" }, + { "Africa/Freetown", "1913-06-01" }, + { "Africa/Gaborone", "1885-01-01" }, + { "Africa/Johannesburg", "1903-03-01" }, + { "Africa/Kampala", "1928-07-01" }, + { "Africa/Kampala", "1948-01-01" }, + { "Africa/Kampala", "1957-01-01" }, + { "Africa/Lagos", "1919-09-01" }, + { "Africa/Libreville", "1912-01-01" }, + { "Africa/Luanda", "1911-05-26" }, + { "Africa/Lubumbashi", "1897-11-09" }, + { "Africa/Lusaka", "1903-03-01" }, + { "Africa/Malabo", "1963-12-15" }, + { "Africa/Maseru", "1903-03-01" }, + { "Africa/Mogadishu", "1957-01-01" }, + { "Africa/Monrovia", "1919-03-01" }, + { "Africa/Nairobi", "1928-07-01" }, + { "Africa/Nairobi", "1940-01-01" }, + { "Africa/Nairobi", "1960-01-01" }, + { "Africa/Niamey", "1960-01-01" }, + { "Africa/Porto-Novo", "1934-02-26" }, + { "Africa/Tripoli", "1920-01-01" }, + { "Africa/Tripoli", "1959-01-01" }, + { "Africa/Tripoli", "1990-05-04" }, + { "Africa/Tunis", "1911-03-11" }, + { "Africa/Windhoek", "1892-02-08" }, + { "Africa/Windhoek", "1903-03-01" }, + { "America/Antigua", "1912-03-02" }, + { "America/Argentina/Buenos_Aires", "1894-10-31" }, + { "America/Argentina/Catamarca", "1991-10-20" }, + { "America/Argentina/Catamarca", "2004-06-01" }, + { "America/Argentina/ComodRivadavia", "1991-10-20" }, + { "America/Argentina/ComodRivadavia", "2004-06-01" }, + { "America/Argentina/Cordoba", "1991-10-20" }, + { "America/Argentina/Jujuy", "1991-10-06" }, + { "America/Argentina/La_Rioja", "2004-06-01" }, + { "America/Argentina/Mendoza", "1992-10-18" }, + { "America/Argentina/Mendoza", "2004-05-23" }, + { "America/Argentina/Rio_Gallegos", "2004-06-01" }, + { "America/Argentina/Salta", "1991-10-20" }, + { "America/Argentina/San_Juan", "2004-05-31" }, + { "America/Argentina/San_Luis", "2004-05-31" }, + { "America/Argentina/San_Luis", "2008-01-21" }, + { "America/Argentina/Tucuman", "1991-10-20" }, + { "America/Argentina/Tucuman", "2004-06-01" }, + { "America/Argentina/Ushuaia", "2004-05-30" }, + { "America/Asuncion", "1931-10-10" }, + { "America/Asuncion", "1974-04-01" }, + { "America/Bahia", "1914-01-01" }, + { "America/Bahia_Banderas", "1930-11-15" }, + { "America/Bahia_Banderas", "1931-10-01" }, + { "America/Bahia_Banderas", "1942-04-24" }, + { "America/Bahia_Banderas", "1949-01-14" }, + { "America/Barbados", "1932-01-01" }, + { "America/Belize", "1912-04-01" }, + { "America/Blanc-Sablon", "1884-01-01" }, + { "America/Bogota", "1914-11-23" }, + { "America/Buenos_Aires", "1894-10-31" }, + { "America/Cambridge_Bay", "2000-11-05" }, + { "America/Campo_Grande", "1914-01-01" }, + { "America/Caracas", "1912-02-12" }, + { "America/Catamarca", "1991-10-20" }, + { "America/Catamarca", "2004-06-01" }, + { "America/Cayenne", "1911-07-01" }, + { "America/Chihuahua", "1930-11-15" }, + { "America/Chihuahua", "1931-10-01" }, + { "America/Cordoba", "1991-10-20" }, + { "America/Costa_Rica", "1921-01-15" }, + { "America/Cuiaba", "1914-01-01" }, + { "America/Danmarkshavn", "1916-07-28" }, + { "America/Detroit", "1905-01-01" }, + { "America/Eirunepe", "1914-01-01" }, + { "America/El_Salvador", "1921-01-01" }, + { "America/Ensenada", "1924-01-01" }, + { "America/Ensenada", "1930-11-15" }, + { "America/Fortaleza", "1914-01-01" }, + { "America/Glace_Bay", "1902-06-15" }, + { "America/Grand_Turk", "1890-01-01" }, + { "America/Guyana", "1991-01-01" }, + { "America/Havana", "1890-01-01" }, + { "America/Hermosillo", "1930-11-15" }, + { "America/Hermosillo", "1931-10-01" }, + { "America/Hermosillo", "1942-04-24" }, + { "America/Hermosillo", "1949-01-14" }, + { "America/Jujuy", "1991-10-06" }, + { "America/Lima", "1890-01-01" }, + { "America/Maceio", "1914-01-01" }, + { "America/Managua", "1890-01-01" }, + { "America/Managua", "1934-06-23" }, + { "America/Managua", "1975-02-16" }, + { "America/Managua", "1992-09-24" }, + { "America/Managua", "1997-01-01" }, + { "America/Mazatlan", "1930-11-15" }, + { "America/Mazatlan", "1931-10-01" }, + { "America/Mazatlan", "1942-04-24" }, + { "America/Mazatlan", "1949-01-14" }, + { "America/Mendoza", "1992-10-18" }, + { "America/Mendoza", "2004-05-23" }, + { "America/Merida", "1982-12-02" }, + { "America/Mexico_City", "1930-11-15" }, + { "America/Mexico_City", "1931-10-01" }, + { "America/Miquelon", "1911-05-15" }, + { "America/Moncton", "1883-12-09" }, + { "America/Montevideo", "1942-12-14" }, + { "America/Montevideo", "1974-12-22" }, + { "America/Montreal", "1884-01-01" }, + { "America/Ojinaga", "1930-11-15" }, + { "America/Ojinaga", "1931-10-01" }, + { "America/Panama", "1890-01-01" }, + { "America/Paramaribo", "1911-01-01" }, + { "America/Porto_Acre", "1914-01-01" }, + { "America/Recife", "1914-01-01" }, + { "America/Regina", "1905-09-01" }, + { "America/Rio_Branco", "1914-01-01" }, + { "America/Rosario", "1991-10-20" }, + { "America/Santa_Isabel", "1924-01-01" }, + { "America/Santa_Isabel", "1930-11-15" }, + { "America/Santarem", "1914-01-01" }, + { "America/Santiago", "1910-01-01" }, + { "America/Santiago", "1919-07-01" }, + { "America/Santo_Domingo", "1890-01-01" }, + { "America/Scoresbysund", "1916-07-28" }, + { "America/Scoresbysund", "1981-03-29" }, + { "America/Tegucigalpa", "1921-04-01" }, + { "America/Thunder_Bay", "1895-01-01" }, + { "America/Tijuana", "1924-01-01" }, + { "America/Tijuana", "1930-11-15" }, + { "Antarctica/Casey", "1969-01-01" }, + { "Antarctica/Casey", "2009-10-18" }, + { "Antarctica/Davis", "1957-01-13" }, + { "Antarctica/Davis", "1969-02-01" }, + { "Antarctica/Davis", "2010-03-11" }, + { "Antarctica/DumontDUrville", "1947-01-01" }, + { "Antarctica/DumontDUrville", "1956-11-01" }, + { "Antarctica/Macquarie", "1911-01-01" }, + { "Antarctica/Mawson", "1954-02-13" }, + { "Antarctica/McMurdo", "1956-01-01" }, + { "Antarctica/Palmer", "1982-05-01" }, + { "Antarctica/South_Pole", "1956-01-01" }, + { "Antarctica/Syowa", "1957-01-29" }, + { "Antarctica/Vostok", "1957-12-16" }, + { "Arctic/Longyearbyen", "1895-01-01" }, + { "Asia/Almaty", "1930-06-21" }, + { "Asia/Anadyr", "1924-05-02" }, + { "Asia/Anadyr", "1930-06-21" }, + { "Asia/Anadyr", "1992-01-19" }, + { "Asia/Anadyr", "2011-03-27" }, + { "Asia/Aqtau", "1924-05-02" }, + { "Asia/Aqtau", "1930-06-21" }, + { "Asia/Aqtau", "1981-10-01" }, + { "Asia/Aqtau", "2005-03-15" }, + { "Asia/Aqtobe", "1924-05-02" }, + { "Asia/Aqtobe", "1930-06-21" }, + { "Asia/Ashgabat", "1924-05-02" }, + { "Asia/Ashgabat", "1930-06-21" }, + { "Asia/Ashgabat", "1992-01-19" }, + { "Asia/Ashkhabad", "1924-05-02" }, + { "Asia/Ashkhabad", "1930-06-21" }, + { "Asia/Ashkhabad", "1992-01-19" }, + { "Asia/Baghdad", "1918-01-01" }, + { "Asia/Bahrain", "1920-01-01" }, + { "Asia/Baku", "1957-03-01" }, + { "Asia/Bangkok", "1920-04-01" }, + { "Asia/Bishkek", "1924-05-02" }, + { "Asia/Bishkek", "1930-06-21" }, + { "Asia/Brunei", "1933-01-01" }, + { "Asia/Calcutta", "1941-10-01" }, + { "Asia/Choibalsan", "1978-01-01" }, + { "Asia/Chongqing", "1980-05-01" }, + { "Asia/Chungking", "1980-05-01" }, + { "Asia/Colombo", "1880-01-01" }, + { "Asia/Colombo", "1906-01-01" }, + { "Asia/Colombo", "1942-09-01" }, + { "Asia/Colombo", "1996-05-25" }, + { "Asia/Dacca", "1941-10-01" }, + { "Asia/Dacca", "1942-09-01" }, + { "Asia/Dhaka", "1941-10-01" }, + { "Asia/Dhaka", "1942-09-01" }, + { "Asia/Dili", "2000-09-17" }, + { "Asia/Dubai", "1920-01-01" }, + { "Asia/Dushanbe", "1924-05-02" }, + { "Asia/Dushanbe", "1930-06-21" }, + { "Asia/Harbin", "1928-01-01" }, + { "Asia/Harbin", "1940-01-01" }, + { "Asia/Ho_Chi_Minh", "1912-05-01" }, + { "Asia/Hong_Kong", "1904-10-30" }, + { "Asia/Hong_Kong", "1941-12-25" }, + { "Asia/Hovd", "1978-01-01" }, + { "Asia/Irkutsk", "1920-01-25" }, + { "Asia/Irkutsk", "1930-06-21" }, + { "Asia/Irkutsk", "1992-01-19" }, + { "Asia/Irkutsk", "2011-03-27" }, + { "Asia/Istanbul", "1880-01-01" }, + { "Asia/Istanbul", "1910-10-01" }, + { "Asia/Istanbul", "1978-10-15" }, + { "Asia/Jakarta", "1932-11-01" }, + { "Asia/Jakarta", "1942-03-23" }, + { "Asia/Jakarta", "1948-05-01" }, + { "Asia/Jayapura", "1944-09-01" }, + { "Asia/Kabul", "1945-01-01" }, + { "Asia/Kamchatka", "1922-11-10" }, + { "Asia/Kamchatka", "1930-06-21" }, + { "Asia/Kamchatka", "1992-01-19" }, + { "Asia/Kamchatka", "2011-03-27" }, + { "Asia/Karachi", "1907-01-01" }, + { "Asia/Kashgar", "1928-01-01" }, + { "Asia/Kashgar", "1980-05-01" }, + { "Asia/Kathmandu", "1986-01-01" }, + { "Asia/Katmandu", "1986-01-01" }, + { "Asia/Kolkata", "1941-10-01" }, + { "Asia/Krasnoyarsk", "1930-06-21" }, + { "Asia/Krasnoyarsk", "1992-01-19" }, + { "Asia/Krasnoyarsk", "2011-03-27" }, + { "Asia/Kuala_Lumpur", "1901-01-01" }, + { "Asia/Kuala_Lumpur", "1905-06-01" }, + { "Asia/Kuala_Lumpur", "1941-09-01" }, + { "Asia/Kuala_Lumpur", "1942-02-16" }, + { "Asia/Kuala_Lumpur", "1982-01-01" }, + { "Asia/Kuching", "1926-03-01" }, + { "Asia/Kuching", "1933-01-01" }, + { "Asia/Kuching", "1942-02-16" }, + { "Asia/Macao", "1912-01-01" }, + { "Asia/Macau", "1912-01-01" }, + { "Asia/Magadan", "1930-06-21" }, + { "Asia/Magadan", "1992-01-19" }, + { "Asia/Magadan", "2011-03-27" }, + { "Asia/Makassar", "1932-11-01" }, + { "Asia/Makassar", "1942-02-09" }, + { "Asia/Manila", "1942-05-01" }, + { "Asia/Muscat", "1920-01-01" }, + { "Asia/Novokuznetsk", "1920-01-06" }, + { "Asia/Novokuznetsk", "1930-06-21" }, + { "Asia/Novokuznetsk", "1992-01-19" }, + { "Asia/Novokuznetsk", "2011-03-27" }, + { "Asia/Novosibirsk", "1930-06-21" }, + { "Asia/Novosibirsk", "1992-01-19" }, + { "Asia/Novosibirsk", "2011-03-27" }, + { "Asia/Omsk", "1919-11-14" }, + { "Asia/Omsk", "1930-06-21" }, + { "Asia/Omsk", "1992-01-19" }, + { "Asia/Omsk", "2011-03-27" }, + { "Asia/Oral", "1924-05-02" }, + { "Asia/Oral", "1930-06-21" }, + { "Asia/Oral", "2005-03-15" }, + { "Asia/Phnom_Penh", "1906-06-09" }, + { "Asia/Phnom_Penh", "1912-05-01" }, + { "Asia/Pontianak", "1932-11-01" }, + { "Asia/Pontianak", "1942-01-29" }, + { "Asia/Pontianak", "1948-05-01" }, + { "Asia/Pontianak", "1964-01-01" }, + { "Asia/Pyongyang", "1890-01-01" }, + { "Asia/Pyongyang", "1904-12-01" }, + { "Asia/Pyongyang", "1932-01-01" }, + { "Asia/Pyongyang", "1961-08-10" }, + { "Asia/Qatar", "1920-01-01" }, + { "Asia/Qyzylorda", "1930-06-21" }, + { "Asia/Qyzylorda", "1992-01-19" }, + { "Asia/Rangoon", "1920-01-01" }, + { "Asia/Rangoon", "1942-05-01" }, + { "Asia/Saigon", "1912-05-01" }, + { "Asia/Sakhalin", "1945-08-25" }, + { "Asia/Sakhalin", "1992-01-19" }, + { "Asia/Sakhalin", "2011-03-27" }, + { "Asia/Samarkand", "1930-06-21" }, + { "Asia/Seoul", "1890-01-01" }, + { "Asia/Seoul", "1904-12-01" }, + { "Asia/Seoul", "1932-01-01" }, + { "Asia/Seoul", "1961-08-10" }, + { "Asia/Seoul", "1968-10-01" }, + { "Asia/Singapore", "1905-06-01" }, + { "Asia/Singapore", "1941-09-01" }, + { "Asia/Singapore", "1942-02-16" }, + { "Asia/Singapore", "1982-01-01" }, + { "Asia/Tashkent", "1924-05-02" }, + { "Asia/Tashkent", "1930-06-21" }, + { "Asia/Tbilisi", "1924-05-02" }, + { "Asia/Tbilisi", "1957-03-01" }, + { "Asia/Tbilisi", "2005-03-27" }, + { "Asia/Tehran", "1946-01-01" }, + { "Asia/Tehran", "1977-11-01" }, + { "Asia/Thimbu", "1987-10-01" }, + { "Asia/Thimphu", "1987-10-01" }, + { "Asia/Ujung_Pandang", "1932-11-01" }, + { "Asia/Ujung_Pandang", "1942-02-09" }, + { "Asia/Ulaanbaatar", "1978-01-01" }, + { "Asia/Ulan_Bator", "1978-01-01" }, + { "Asia/Urumqi", "1928-01-01" }, + { "Asia/Urumqi", "1980-05-01" }, + { "Asia/Vientiane", "1906-06-09" }, + { "Asia/Vientiane", "1912-05-01" }, + { "Asia/Vladivostok", "1922-11-15" }, + { "Asia/Vladivostok", "1930-06-21" }, + { "Asia/Vladivostok", "1992-01-19" }, + { "Asia/Vladivostok", "2011-03-27" }, + { "Asia/Yakutsk", "1930-06-21" }, + { "Asia/Yakutsk", "1992-01-19" }, + { "Asia/Yakutsk", "2011-03-27" }, + { "Asia/Yekaterinburg", "1930-06-21" }, + { "Asia/Yekaterinburg", "1992-01-19" }, + { "Asia/Yekaterinburg", "2011-03-27" }, + { "Asia/Yerevan", "1924-05-02" }, + { "Asia/Yerevan", "1957-03-01" }, + { "Atlantic/Azores", "1884-01-01" }, + { "Atlantic/Azores", "1911-05-24" }, + { "Atlantic/Azores", "1942-04-25" }, + { "Atlantic/Azores", "1943-04-17" }, + { "Atlantic/Azores", "1944-04-22" }, + { "Atlantic/Azores", "1945-04-21" }, + { "Atlantic/Cape_Verde", "1907-01-01" }, + { "Atlantic/Jan_Mayen", "1895-01-01" }, + { "Atlantic/Madeira", "1942-04-25" }, + { "Atlantic/Madeira", "1943-04-17" }, + { "Atlantic/Madeira", "1944-04-22" }, + { "Atlantic/Madeira", "1945-04-21" }, + { "Atlantic/Reykjavik", "1837-01-01" }, + { "Atlantic/Stanley", "1912-03-12" }, + { "Australia/Adelaide", "1899-05-01" }, + { "Australia/Broken_Hill", "1895-02-01" }, + { "Australia/Broken_Hill", "1899-05-01" }, + { "Australia/Currie", "1895-09-01" }, + { "Australia/Darwin", "1895-02-01" }, + { "Australia/Darwin", "1899-05-01" }, + { "Australia/Eucla", "1895-12-01" }, + { "Australia/Hobart", "1895-09-01" }, + { "Australia/LHI", "1981-03-01" }, + { "Australia/Lindeman", "1895-01-01" }, + { "Australia/Lord_Howe", "1981-03-01" }, + { "Australia/Melbourne", "1895-02-01" }, + { "Australia/North", "1895-02-01" }, + { "Australia/North", "1899-05-01" }, + { "Australia/Perth", "1895-12-01" }, + { "Australia/South", "1899-05-01" }, + { "Australia/Tasmania", "1895-09-01" }, + { "Australia/Victoria", "1895-02-01" }, + { "Australia/West", "1895-12-01" }, + { "Australia/Yancowinna", "1895-02-01" }, + { "Australia/Yancowinna", "1899-05-01" }, + { "Brazil/Acre", "1914-01-01" }, + { "Canada/East-Saskatchewan", "1905-09-01" }, + { "Canada/Saskatchewan", "1905-09-01" }, + { "Chile/Continental", "1910-01-01" }, + { "Chile/Continental", "1919-07-01" }, + { "Chile/EasterIsland", "1932-09-01" }, + { "Cuba", "1890-01-01" }, + { "Eire", "1880-08-02" }, + { "Europe/Amsterdam", "1937-07-01" }, + { "Europe/Andorra", "1946-09-30" }, + { "Europe/Athens", "1916-07-28" }, + { "Europe/Athens", "1944-04-04" }, + { "Europe/Berlin", "1893-04-01" }, + { "Europe/Bratislava", "1891-10-01" }, + { "Europe/Brussels", "1914-11-08" }, + { "Europe/Bucharest", "1931-07-24" }, + { "Europe/Chisinau", "1931-07-24" }, + { "Europe/Copenhagen", "1894-01-01" }, + { "Europe/Dublin", "1880-08-02" }, + { "Europe/Gibraltar", "1941-05-04" }, + { "Europe/Gibraltar", "1942-04-05" }, + { "Europe/Gibraltar", "1943-04-04" }, + { "Europe/Gibraltar", "1944-04-02" }, + { "Europe/Gibraltar", "1945-04-02" }, + { "Europe/Gibraltar", "1947-04-13" }, + { "Europe/Helsinki", "1921-05-01" }, + { "Europe/Istanbul", "1880-01-01" }, + { "Europe/Istanbul", "1910-10-01" }, + { "Europe/Istanbul", "1978-10-15" }, + { "Europe/Kaliningrad", "1945-01-01" }, + { "Europe/Kaliningrad", "1946-01-01" }, + { "Europe/Kaliningrad", "2011-03-27" }, + { "Europe/Kiev", "1930-06-21" }, + { "Europe/Kiev", "1943-11-06" }, + { "Europe/Luxembourg", "1904-06-01" }, + { "Europe/Madrid", "1942-05-02" }, + { "Europe/Madrid", "1943-04-17" }, + { "Europe/Madrid", "1944-04-15" }, + { "Europe/Madrid", "1945-04-14" }, + { "Europe/Madrid", "1946-04-13" }, + { "Europe/Malta", "1893-11-02" }, + { "Europe/Mariehamn", "1921-05-01" }, + { "Europe/Minsk", "1924-05-02" }, + { "Europe/Minsk", "1930-06-21" }, + { "Europe/Minsk", "2011-03-27" }, + { "Europe/Monaco", "1941-05-05" }, + { "Europe/Monaco", "1942-03-09" }, + { "Europe/Monaco", "1943-03-29" }, + { "Europe/Monaco", "1944-04-03" }, + { "Europe/Monaco", "1945-04-02" }, + { "Europe/Moscow", "1916-07-03" }, + { "Europe/Moscow", "1919-05-31" }, + { "Europe/Moscow", "1930-06-21" }, + { "Europe/Moscow", "1992-01-19" }, + { "Europe/Moscow", "2011-03-27" }, + { "Europe/Oslo", "1895-01-01" }, + { "Europe/Paris", "1945-04-02" }, + { "Europe/Prague", "1891-10-01" }, + { "Europe/Riga", "1926-05-11" }, + { "Europe/Riga", "1940-08-05" }, + { "Europe/Riga", "1944-10-13" }, + { "Europe/Rome", "1893-11-01" }, + { "Europe/Samara", "1930-06-21" }, + { "Europe/Samara", "1991-10-20" }, + { "Europe/Samara", "2011-03-27" }, + { "Europe/San_Marino", "1893-11-01" }, + { "Europe/Simferopol", "1930-06-21" }, + { "Europe/Simferopol", "1994-05-01" }, + { "Europe/Sofia", "1880-01-01" }, + { "Europe/Sofia", "1894-11-30" }, + { "Europe/Tallinn", "1919-07-01" }, + { "Europe/Tallinn", "1921-05-01" }, + { "Europe/Tallinn", "1940-08-06" }, + { "Europe/Tiraspol", "1931-07-24" }, + { "Europe/Uzhgorod", "1945-06-29" }, + { "Europe/Vaduz", "1894-06-01" }, + { "Europe/Vatican", "1893-11-01" }, + { "Europe/Vilnius", "1917-01-01" }, + { "Europe/Vilnius", "1920-07-12" }, + { "Europe/Vilnius", "1940-08-03" }, + { "Europe/Volgograd", "1920-01-03" }, + { "Europe/Volgograd", "1930-06-21" }, + { "Europe/Volgograd", "1991-03-31" }, + { "Europe/Volgograd", "2011-03-27" }, + { "Europe/Zaporozhye", "1930-06-21" }, + { "Europe/Zaporozhye", "1943-10-25" }, + { "Europe/Zurich", "1894-06-01" }, + { "Hongkong", "1904-10-30" }, + { "Hongkong", "1941-12-25" }, + { "Iceland", "1837-01-01" }, + { "Indian/Chagos", "1907-01-01" }, + { "Indian/Chagos", "1996-01-01" }, + { "Indian/Cocos", "1900-01-01" }, + { "Indian/Comoro", "1911-07-01" }, + { "Indian/Kerguelen", "1950-01-01" }, + { "Indian/Mahe", "1906-06-01" }, + { "Indian/Maldives", "1960-01-01" }, + { "Indian/Mauritius", "1907-01-01" }, + { "Indian/Reunion", "1911-06-01" }, + { "Iran", "1946-01-01" }, + { "Iran", "1977-11-01" }, + { "Libya", "1920-01-01" }, + { "Libya", "1959-01-01" }, + { "Libya", "1990-05-04" }, + { "Mexico/BajaNorte", "1924-01-01" }, + { "Mexico/BajaNorte", "1930-11-15" }, + { "Mexico/BajaSur", "1930-11-15" }, + { "Mexico/BajaSur", "1931-10-01" }, + { "Mexico/BajaSur", "1942-04-24" }, + { "Mexico/BajaSur", "1949-01-14" }, + { "Mexico/General", "1930-11-15" }, + { "Mexico/General", "1931-10-01" }, + { "NZ-CHAT", "1957-01-01" }, + { "Pacific/Apia", "1911-01-01" }, + { "Pacific/Apia", "2011-12-30" }, + { "Pacific/Chatham", "1957-01-01" }, + { "Pacific/Easter", "1932-09-01" }, + { "Pacific/Enderbury", "1901-01-01" }, + { "Pacific/Enderbury", "1995-01-01" }, + { "Pacific/Fakaofo", "2011-12-30" }, + { "Pacific/Fiji", "1915-10-26" }, + { "Pacific/Funafuti", "1901-01-01" }, + { "Pacific/Galapagos", "1986-01-01" }, + { "Pacific/Gambier", "1912-10-01" }, + { "Pacific/Guadalcanal", "1912-10-01" }, + { "Pacific/Guam", "1901-01-01" }, + { "Pacific/Kiritimati", "1901-01-01" }, + { "Pacific/Kiritimati", "1995-01-01" }, + { "Pacific/Kosrae", "1901-01-01" }, + { "Pacific/Kosrae", "1969-10-01" }, + { "Pacific/Kwajalein", "1993-08-20" }, + { "Pacific/Majuro", "1969-10-01" }, + { "Pacific/Marquesas", "1912-10-01" }, + { "Pacific/Nauru", "1921-01-15" }, + { "Pacific/Nauru", "1944-08-15" }, + { "Pacific/Nauru", "1979-05-01" }, + { "Pacific/Niue", "1901-01-01" }, + { "Pacific/Niue", "1951-01-01" }, + { "Pacific/Norfolk", "1901-01-01" }, + { "Pacific/Norfolk", "1951-01-01" }, + { "Pacific/Pago_Pago", "1911-01-01" }, + { "Pacific/Palau", "1901-01-01" }, + { "Pacific/Pohnpei", "1901-01-01" }, + { "Pacific/Ponape", "1901-01-01" }, + { "Pacific/Port_Moresby", "1895-01-01" }, + { "Pacific/Rarotonga", "1978-11-12" }, + { "Pacific/Saipan", "1969-10-01" }, + { "Pacific/Samoa", "1911-01-01" }, + { "Pacific/Tahiti", "1912-10-01" }, + { "Pacific/Tarawa", "1901-01-01" }, + { "Pacific/Tongatapu", "1901-01-01" }, + { "Pacific/Tongatapu", "1941-01-01" }, + { "Pacific/Wake", "1901-01-01" }, + { "ROK", "1890-01-01" }, + { "ROK", "1904-12-01" }, + { "ROK", "1932-01-01" }, + { "ROK", "1961-08-10" }, + { "ROK", "1968-10-01" }, + { "Singapore", "1905-06-01" }, + { "Singapore", "1941-09-01" }, + { "Singapore", "1942-02-16" }, + { "Singapore", "1982-01-01" }, + { "Turkey", "1880-01-01" }, + { "Turkey", "1910-10-01" }, + { "Turkey", "1978-10-15" }, + { "US/Michigan", "1905-01-01" }, + { "US/Samoa", "1911-01-01" }, + { "W-SU", "1916-07-03" }, + { "W-SU", "1930-06-21" }, + { "W-SU", "1992-01-19" }, + { "W-SU", "2011-03-27" } + }; + + LanguageType eLang = LANGUAGE_ENGLISH_US; + SvNumberFormatter aFormatter(m_xContext, eLang); + + for (auto const& aEntry : aData) + { + checkDateInput(aFormatter, aEntry[0], aEntry[1]); + } +} + +void Test::checkDateInput( SvNumberFormatter& rFormatter, const char* pTimezone, const char* pIsoDate ) +{ + icu::TimeZone::adoptDefault( icu::TimeZone::createTimeZone( pTimezone)); + OUString aDate( OUString::createFromAscii(pIsoDate)); + sal_uInt32 nIndex = 0; + double fVal = 0.0; + bool bVal = rFormatter.IsNumberFormat( aDate, nIndex, fVal); + CPPUNIT_ASSERT_MESSAGE( OString(OString::Concat("Date not recognized: ") + + pTimezone + " " + pIsoDate).getStr(), bVal); + CPPUNIT_ASSERT_MESSAGE("Format parsed is not date.", + (rFormatter.GetType(nIndex) & SvNumFormatType::DATE)); + OUString aOutString; + const Color *pColor; + rFormatter.GetOutputString( fVal, nIndex, aOutString, &pColor); + CPPUNIT_ASSERT_EQUAL( aDate, aOutString); +} + +void Test::testIsNumberFormat() +{ + LanguageType eLang = LANGUAGE_ENGLISH_US; + SvNumberFormatter aFormatter(m_xContext, eLang); + + static struct NumberFormatData + { + const char* pFormat; + bool bIsNumber; + } const aTests[] = { + { "20.3", true }, + { "2", true }, + { "test", false }, + { "Jan1", false }, // tdf#34724 + { "1Jan", false }, // tdf#34724 + { "Jan1 2000", true }, // tdf#91420 + { "Jan1, 2000", true }, // tdf#91420 + { "Jan 1", true }, + { "Sept 1", true }, //tdf#127363 + { "5/d", false }, //tdf#143165 + { "Jan 1 2000", true }, + { "5-12-14", false }, + { "005-12-14", true }, + { "15-10-30", true }, + { "2015-10-30", true }, + { "1999-11-23T12:34:56", true }, + { "1999-11-23 12:34:56", true }, + { "1999-11-23T12:34:56.789", true }, + { "1999-11-23T12:34:56,789", true }, // ISO 8601 defines both dot and comma as fractional separator + { "1999-11-23 12:34:56.789", true }, + { "1999-11-23 12:34:56,789", false }, // comma not in en-US if 'T' separator is not present, + // debatable, 'T' "may be omitted by mutual consent of those + // interchanging data, if ambiguity can be avoided." + { "1999-11-23T12:34:56/789", false }, + { "−1000", true } // unicode minus + }; + + for (auto const[pFormat, bTestIsNumber] : aTests) + { + sal_uInt32 nIndex = 0; + double nNumber = 0; + OUString aString = OUString::fromUtf8(pFormat); + bool bIsNumber = aFormatter.IsNumberFormat(aString, nIndex, nNumber); + CPPUNIT_ASSERT_EQUAL_MESSAGE(pFormat, bTestIsNumber, bIsNumber); + } +} + +struct FormatInputOutput +{ + const char* mpInput; + const bool mbNumber; + const char* mpOutput; + const sal_uInt32 mnOutputIndex; +}; + +void checkSpecificNumberFormats( SvNumberFormatter& rFormatter, + const std::vector<FormatInputOutput>& rVec, const char* pName ) +{ + + for (size_t i = 0; i < rVec.size(); ++i) + { + sal_uInt32 nIndex = 0; + double fNumber = 0; + OUString aString( OUString::fromUtf8( rVec[i].mpInput)); + const bool bIsNumber = rFormatter.IsNumberFormat( aString, nIndex, fNumber); + CPPUNIT_ASSERT_EQUAL_MESSAGE( OString( pName + OString::Concat(" ") + OString::number(i) + + (rVec[i].mbNumber ? " not recognized: " : " should not be recognized: ") + + OUStringToOString( aString, RTL_TEXTENCODING_UTF8)).getStr(), rVec[i].mbNumber, bIsNumber); + if (bIsNumber) + { + if (rVec[i].mnOutputIndex) + nIndex = rVec[i].mnOutputIndex; + const Color* pColor; + rFormatter.GetOutputString( fNumber, nIndex, aString, &pColor); + CPPUNIT_ASSERT_EQUAL_MESSAGE( OString( pName + OString::Concat(" ") + OString::number(i) + " mismatch").getStr(), + OUString::fromUtf8( rVec[i].mpOutput), aString); + } + } +} + +void Test::testIsNumberFormatSpecific() +{ + { + // en-US uses M/D/Y format, test that a-b-c input with a<=31 and b<=12 + // does not lead to a/b/c date output + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_ENGLISH_US); + + std::vector<FormatInputOutput> aIO = { + { "5-12-14", false, "", 0 }, + { "32-12-14", true, "1932-12-14", 0 } + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[en-US] date"); + } + + { + // de-DE uses D.M.Y format, test that a-b-c input with a<=31 and b<=12 + // does not lead to a.b.c date output + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_GERMAN); + + std::vector<FormatInputOutput> aIO = { + { "5-12-14", false, "", 0 }, + { "32-12-14", true, "1932-12-14", 0 } + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[de-DE] date"); + } + + { + // nl-NL uses D-M-Y format, test that D-M-Y input leads to D-M-Y output + // and ISO Y-M-D input leads to Y-M-D output. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_DUTCH); + + std::vector<FormatInputOutput> aIO = { + { "22-11-1999", true, "22-11-99", 0 }, // if default YY changes to YYYY adapt this + { "1999-11-22", true, "1999-11-22", 0 }, + { "1-2-11", true, "01-02-11", 0 }, // if default YY changes to YYYY adapt this + { "99-2-11", true, "1999-02-11", 0 } + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[nl-NL] date"); + } + + { + // en-ZA uses Y-M-D and Y/M/D format, test that either are accepted. + // The default format changed from YY/MM/DD to YYYY-MM-DD. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_ENGLISH_SAFRICA); + + std::vector<FormatInputOutput> aIO = { + { "1999/11/22", true, "1999-11-22", 0 }, + { "1999-11-22", true, "1999-11-22", 0 }, + { "11/2/1", true, "2011-02-01", 0 }, + { "99-2-11", true, "1999-02-11", 0 }, + { "22-2-11", true, "2022-02-11", 0 }, + { "02 Mar 2020",true, "2020-03-02", 0 } + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[en-ZA] date"); + } + + { + // fr-FR uses D/M/Y format with additional D.M.Y and D-M-Y date + // acceptance patterns, test combinations. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_FRENCH); + + std::vector<FormatInputOutput> aIO = { + { "22/11/1999", true, "22/11/99", 0 }, // if default YY changes to YYYY adapt this + { "1999-11-22", true, "1999-11-22", 0 }, + { "1/2/11", true, "01/02/11", 0 }, // if default YY changes to YYYY adapt this + { "99-2-11", true, "1999-02-11", 0 }, + { "22-2-11", true, "22/02/11", 0 }, // if default YY changes to YYYY adapt this + { "22.2.11", true, "22/02/11", 0 } // if default YY changes to YYYY adapt this + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[fr-FR] date"); + } + + { + // Test Spanish "mar" short name ambiguity, day "martes" or month "marzo". + // Day of week names are only parsed away, not evaluated if they actually + // correspond to the date given. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_SPANISH); + + const sal_uInt32 n = aFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, LANGUAGE_SPANISH); + std::vector<FormatInputOutput> aIO = { + { "22/11/1999", true, "22/11/1999", n }, + { "Lun 22/11/1999", true, "22/11/1999", n }, + { "Mar 22/11/1999", true, "22/11/1999", n }, + { "Abr 22/11/1999", false, "", n }, // month name AND numeric month don't go along + { "Lun Mar 22/11/1999", false, "", n }, // month name AND numeric month don't go along + { "Mar Mar 22/11/1999", false, "", n }, // month name AND numeric month don't go along + { "Lun Mar 22 1999", true, "22/03/1999", n }, + { "Mar Mar 22 1999", true, "22/03/1999", n }, + { "Mar Lun 22 1999", false, "", n } // day name only at the beginning (could change?) + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[es-ES] date"); + } + + { + // Test that de-DE accepts Januar and Jänner. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_GERMAN); + + const sal_uInt32 n = aFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, LANGUAGE_GERMAN); + std::vector<FormatInputOutput> aIO = { + { "23. Januar 1999", true, "23.01.1999", n }, + { "23. J\xC3\xA4nner 1999", true, "23.01.1999", n }, + { "23. Jan. 1999", true, "23.01.1999", n }, + { "23. J\xC3\xA4n. 1999", true, "23.01.1999", n }, + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[de-DE] date January month names"); + } + + { + // tdf#143664 + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_GERMAN); + + const sal_uInt32 n = aFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, LANGUAGE_GERMAN); + std::vector<FormatInputOutput> aIO = { + { "23. M\u00C4R 1999", true, "23.03.1999", n }, + { "23. M\u00C4RZ 1999", true, "23.03.1999", n }, + { "23. MRZ 1999", true, "23.03.1999", n }, + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[de-DE] date March month names"); + } + + { + // Test that de-AT accepts Januar and Jänner. + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_GERMAN_AUSTRIAN); + + const sal_uInt32 n = aFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, LANGUAGE_GERMAN_AUSTRIAN); + std::vector<FormatInputOutput> aIO = { + { "23. Januar 1999", true, "23.01.1999", n }, + { "23. J\xC3\xA4nner 1999", true, "23.01.1999", n }, + { "23. Jan. 1999", true, "23.01.1999", n }, + { "23. J\xC3\xA4n. 1999", true, "23.01.1999", n }, + }; + + checkSpecificNumberFormats( aFormatter, aIO, "[de-AT] date January month names"); + } +} + +void Test::testUserDefinedNumberFormats() +{ + LanguageType eLang = LANGUAGE_ENGLISH_US; + OUString sCode, sExpected; + SvNumberFormatter aFormatter(m_xContext, eLang); + { // tdf#97835: suppress decimal separator + sCode = "0.##\" m\""; + sExpected = "12 m"; + checkPreviewString(aFormatter, sCode, 12.0, eLang, sExpected); + } + { // tdf#61996: skip quoted text + sCode = "0.00\" ;\""; + sExpected = "-12.00 ;"; + checkPreviewString(aFormatter, sCode, -12.0, eLang, sExpected); + } + { // tdf#100755 + sCode = "000\" \"000/000"; + sExpected = "003 016/113"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#100834 + sCode = "#\" string \"?/???"; + sExpected = "3 string 16/113"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#129878 + sCode = "[HH]"; + sExpected = "#FMT"; + checkPreviewString(aFormatter, sCode, 2E+306, eLang, sExpected); + } + { // tdf#144697 + sCode = "YYYY-MM-DD"; + sExpected = "#FMT"; + checkPreviewString(aFormatter, sCode, -12662108.0, eLang, sExpected); + } + { // tdf#122991 + sCode = "[HH]:MM:SS"; + sExpected = "08:47:00"; + checkPreviewString(aFormatter, sCode, 0.365972222222222, eLang, sExpected); + + sCode = "HH:MM:SS"; + checkPreviewString(aFormatter, sCode, 0.365972222222222, eLang, sExpected); + } + { // tdf#100122 + sCode = "?/?"; + sExpected = "-1/2"; + checkPreviewString(aFormatter, sCode, -0.5, eLang, sExpected); + } + { // tdf#52510 + sCode = "_($* #,##0.00_);_($* (#,##0.00);"; + sExpected = ""; + checkPreviewString(aFormatter, sCode, 0.0, eLang, sExpected); + } + { // tdf#95339: detect SSMM as second minute + sCode = "SS:MM:HH DD/MM/YY"; // Month not detected by Excel, but we do not follow that. + sExpected = "53:23:03 02/01/00"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#101147: detect SSMM as second month + sCode = "HH:MM:SS MM/DD"; + sExpected = "03:23:53 01/02"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#123748 + sCode = "HH:MM:SS.000000"; + sExpected = "12:54:00.000000"; + checkPreviewString(aFormatter, sCode, 43521.5375, eLang, sExpected); + } + { // tdf#101096: different detection of month/minute with Excel + sCode = "HH DD MM"; // month detected because of previous DD + sExpected = "03 02 01"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "HH:MM HH DD/MM"; // month detected because of previous DD + sExpected = "03:23 03 02/01"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "SS:DD-MM-YY SS:MM"; // 1st is month, because of previous DD; 2nd is minute as SS has not minute + sExpected = "53:02-01-00 53:23"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#99996: better algorithm for fraction representation + sCode = "# ?/???"; + sExpected = "-575 540/697"; + checkPreviewString(aFormatter, sCode, -575.774749601315, eLang, sExpected); + } + { // tdf#153887: integer value without integer part displayed + sCode = "#/?"; + sExpected = "2/1"; + checkPreviewString(aFormatter, sCode, 1.95, eLang, sExpected); + checkPreviewString(aFormatter, sCode, 2.00, eLang, sExpected); + checkPreviewString(aFormatter, sCode, 2.05, eLang, sExpected); + sCode = "0/8"; + sExpected = "16/8"; + checkPreviewString(aFormatter, sCode, 1.95, eLang, sExpected); + checkPreviewString(aFormatter, sCode, 2.00, eLang, sExpected); + checkPreviewString(aFormatter, sCode, 2.05, eLang, sExpected); + } + { // tdf#102507: left alignment of denominator + sCode = "# ?/???"; + sExpected = "3 1/2 "; + checkPreviewString(aFormatter, sCode, 3.5, eLang, sExpected); + } + { // tdf#100594: forced denominator + sCode = "# ?/100"; + sExpected = " 6/100"; + checkPreviewString(aFormatter, sCode, 0.06, eLang, sExpected); + } + { // tdf#100754: forced denominator with text after fraction + sCode = "# ?/16\" inch\""; + sExpected = "2 6/16 inch"; + checkPreviewString(aFormatter, sCode, 2.379, eLang, sExpected); + } + { // tdf#100842: text before/after fraction + sCode = "\"before \"?/?\" after\""; + sExpected = "before 11/9 after"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "\"before \"# ?/?\" after\""; + sExpected = "before 1 2/9 after"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "\"before \"0.0\"inside\"0E+0\"middle\"0\" after\""; + sExpected = "before 1.2inside3E+0middle4 after"; + checkPreviewString(aFormatter, sCode, 12345.667, eLang, sExpected); + } + { // tdf#106190: text after fraction bar + sCode = "?/ ?"; + sExpected = "11/ 9"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "?/ 12"; + sExpected = "15/ 12"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "# ?/\" divisor \"?"; + sExpected = "1 2/ divisor 9"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "# ?/\"divided by \"?"; + sExpected = "1 2/divided by 9"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "?/\" \"12"; + sExpected = "15/ 12"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "?/\\ 12"; + sExpected = "15/ 12"; + checkPreviewString(aFormatter, sCode, 1.2345667, eLang, sExpected); + sCode = "# ?/ ???"; + sExpected = "3 1/ 2 "; + checkPreviewString(aFormatter, sCode, 3.5, eLang, sExpected); + } + { // Display 1.96 as 2 and not 1 1/1 + sCode = "# ?/?"; + sExpected = "2 "; + checkPreviewString(aFormatter, sCode, 1.96, eLang, sExpected); + sCode = "# ?/ ?"; + sExpected = "2 "; + checkPreviewString(aFormatter, sCode, 1.96, eLang, sExpected); + sCode = "# #/#"; + sExpected = "2"; + checkPreviewString(aFormatter, sCode, 1.96, eLang, sExpected); + } + { // tdf#79399 tdf#101462 Native Number Formats + sCode = "[NatNum5][$-0404]General\\ "; + // Chinese upper case number characters for 120 + sExpected = u"\u58F9\u4F70\u8CB3\u62FE "_ustr; + checkPreviewString(aFormatter, sCode, 120, eLang, sExpected); + sCode = "[DBNum2][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 120, eLang, sExpected); + // tdf#115007 - cardinal/ordinal number names/indicators + sCode = "[NatNum12]0"; + sExpected = "one hundred twenty-three"; + checkPreviewString(aFormatter, sCode, 123, eLang, sExpected); + sCode = "[NatNum12]0.00"; + sExpected = "one hundred twenty-three point four five"; + checkPreviewString(aFormatter, sCode, 123.45, eLang, sExpected); + sCode = "[NatNum12 ordinal]0"; + sExpected = "one hundred twenty-third"; + checkPreviewString(aFormatter, sCode, 123, eLang, sExpected); + sCode = "[NatNum12 ordinal-number]0"; + sExpected = "123rd"; + checkPreviewString(aFormatter, sCode, 123, eLang, sExpected); + sCode = "[NatNum12 capitalize]0"; + sExpected = "One hundred twenty-three"; + checkPreviewString(aFormatter, sCode, 123, eLang, sExpected); + sCode = "[NatNum12 title ordinal]0"; + sExpected = "One Thousand Two Hundred Thirty-Fourth"; + checkPreviewString(aFormatter, sCode, 1234, eLang, sExpected); + sCode = "[NatNum12 upper ordinal-number]0"; + sExpected = "12345TH"; + checkPreviewString(aFormatter, sCode, 12345, eLang, sExpected); + sCode = "[NatNum12 D=ordinal-number]D\" of \"MMMM"; + sExpected = "2nd of January"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 D=ordinal-number,YYYY=year]D\" of \"MMMM\", \"YYYY"; + sExpected = "2nd of January, nineteen hundred"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 YYYY=title year, D=capitalize ordinal]D\" of \"MMMM\", \"YYYY"; + sExpected = "Second of January, Nineteen Hundred"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 MMMM=upper MMM=upper MMMMM=upper]MMMM MMM MMMMM"; + sExpected = "JANUARY JAN J"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 DDDD=upper DDD=upper]DDDD DDD"; + sExpected = "TUESDAY TUE"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 NNN=upper NN=upper]NNN NN"; + sExpected = "TUESDAY TUE"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 MMMM=lower MMM=lower MMMMM=lower]MMMM MMM MMMMM"; + sExpected = "january jan j"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 DDDD=lower DDD=lower]DDDD DDD"; + sExpected = "tuesday tue"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "[NatNum12 NNN=lower NN=lower]NNN NN"; + sExpected = "tuesday tue"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#130193 tdf#130140 Native Number Formats mapping for Chinese (Traditional), Japanese, Korean + // -- Traditional Chinese: DBNum1 -> NatNum4, DBNum2 -> NatNum5, DBnum3 -> NatNum3 + + // DBNum1 -> NatNum4: Chinese lower case text for 123456789 + // 一億二千三百四十五萬六千七百八十九 + sExpected = u"\u4e00\u5104\u4e8c\u5343\u4e09\u767e\u56db\u5341\u4e94\u842c\u516d\u5343" + u"\u4e03\u767e\u516b\u5341\u4e5d "_ustr; + sCode = "[NatNum4][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum1][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum2 -> NatNum5: Chinese upper case text + // 壹億貳仟參佰肆拾伍萬陸仟柒佰捌拾玖 + sExpected = u"\u58f9\u5104\u8cb3\u4edf\u53c3\u4f70\u8086\u62fe\u4f0d\u842c\u9678\u4edf" + u"\u67d2\u4f70\u634c\u62fe\u7396 "_ustr; + sCode = "[NatNum5][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum2][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum3 -> NatNum3: fullwidth text + // 123456789 + sExpected = u"\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19 "_ustr; + sCode = "[NatNum3][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum3][$-0404]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // -- Japanese: DBNum1 -> NatNum4, DBNum2 -> NatNum5, DBnum3 -> NatNum3 + + // DBNum1 -> NatNum4: Japanese modern long Kanji text for 123456789 + // 一億二千三百四十五万六千七百八十九 + sExpected = u"\u4e00\u5104\u4e8c\u5343\u4e09\u767e\u56db\u5341\u4e94\u4e07\u516d\u5343" + u"\u4e03\u767e\u516b\u5341\u4e5d "_ustr; + sCode = "[NatNum4][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum1][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum2 -> NatNum5: traditional long Kanji text + // 壱億弐阡参百四拾伍萬六阡七百八拾九 + sExpected = u"\u58f1\u5104\u5f10\u9621\u53c2\u767e\u56db\u62fe\u4f0d\u842c\u516d\u9621" + u"\u4e03\u767e\u516b\u62fe\u4e5d "_ustr; + sCode = "[NatNum5][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum2][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum3 -> NatNum3: fullwidth Arabic digits + // 123456789 + sExpected = u"\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19 "_ustr; + sCode = "[NatNum3][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum3][$-0411]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // -- Korean: DBNum1 -> NatNum4, DBNum2 -> NatNum5, DBNum3 -> NatNum6, DBNum4 -> NatNum10 + + // DBNum1 -> NatNum4: Korean lower case characters + // 一億二千三百四十五万六千七百八十九 + sExpected = u"\u4e00\u5104\u4e8c\u5343\u4e09\u767e\u56db\u5341\u4e94\u4e07\u516d\u5343\u4e03\u767e\u516b\u5341\u4e5d "_ustr; + sCode = "[NatNum4][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum1][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum2 -> NatNum5: Korean upper case characters + // 壹億貳阡參佰四拾伍萬六阡七佰八拾九 + sExpected = u"\u58f9\u5104\u8cb3\u9621\u53c3\u4f70\u56db\u62fe\u4f0d\u842c\u516d\u9621\u4e03\u4f70\u516b\u62fe\u4e5d "_ustr; + sCode = "[NatNum5][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum2][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum3 -> NatNum6: fullwidth Arabic digits + // 1억2천3백4십5만6천7백8십9 + sExpected = u"\uff11\uc5b5\uff12\ucc9c\uff13\ubc31\uff14\uc2ed\uff15\ub9cc\uff16\ucc9c\uff17\ubc31\uff18\uc2ed\uff19 "_ustr; + sCode = "[NatNum6][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum3][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + + // DBNum4 -> NatNum10: Hangul characters + // 일억이천삼백사십오만육천칠백팔십구 + sExpected = u"\uc77c\uc5b5\uc774\ucc9c\uc0bc\ubc31\uc0ac\uc2ed\uc624\ub9cc\uc721\ucc9c\uce60\ubc31\ud314\uc2ed\uad6c "_ustr; + sCode = "[NatNum10][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + sCode = "[DBNum4][$-0412]General\\ "; + checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected); + } + { // tdf#105968 engineering format with value rounded up to next magnitude + sCode = "##0.00E+00"; + sExpected = "100.00E+00"; + checkPreviewString(aFormatter, sCode, 99.995, eLang, sExpected); + // test '1'=='1' assumption + checkPreviewString(aFormatter, sCode, 100.0, eLang, sExpected); + sExpected = "199.99E+00"; + checkPreviewString(aFormatter, sCode, 199.99, eLang, sExpected); + sExpected = "1.00E+03"; + checkPreviewString(aFormatter, sCode, 1000.0, eLang, sExpected); + // and another just "normally" rounded value + sExpected = "894.55E-06"; + checkPreviewString(aFormatter, sCode, 0.000894549, eLang, sExpected); + // not expecting rounding into another magnitude + sExpected = "999.99E-06"; + checkPreviewString(aFormatter, sCode, 0.000999991, eLang, sExpected); + // expecting rounding into another magnitude + sExpected = "1.00E-03"; + checkPreviewString(aFormatter, sCode, 0.000999999, eLang, sExpected); + + // Now the same all negative values. + sExpected = "-100.00E+00"; + checkPreviewString(aFormatter, sCode, -99.995, eLang, sExpected); + checkPreviewString(aFormatter, sCode, -100.0, eLang, sExpected); + sExpected = "-199.99E+00"; + checkPreviewString(aFormatter, sCode, -199.99, eLang, sExpected); + sExpected = "-1.00E+03"; + checkPreviewString(aFormatter, sCode, -1000.0, eLang, sExpected); + sExpected = "-894.55E-06"; + checkPreviewString(aFormatter, sCode, -0.000894549, eLang, sExpected); + sExpected = "-999.99E-06"; + checkPreviewString(aFormatter, sCode, -0.000999991, eLang, sExpected); + sExpected = "-1.00E-03"; + checkPreviewString(aFormatter, sCode, -0.000999999, eLang, sExpected); + } + { // tdf#112933 one decimal seconds fraction + sCode = "MM:SS.0"; + sExpected = "23:53.6"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // Two decimals. + sCode = "MM:SS.00"; + sExpected = "23:53.61"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // Three decimals. + sCode = "MM:SS.000"; + sExpected = "23:53.605"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + + // Same with date+time. + sCode = "YYYY-MM-DD MM:SS.0"; + sExpected = "1900-01-02 23:53.6"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "YYYY-MM-DD MM:SS.00"; + sExpected = "1900-01-02 23:53.61"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "YYYY-MM-DD MM:SS.000"; + sExpected = "1900-01-02 23:53.605"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#150028 decimals of seconds fraction without truncate on overflow + sCode = "[SS]"; + sExpected = "271434"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // One decimal. + sCode = "[SS].0"; + sExpected = "271433.6"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // Two decimals. + sCode = "[SS].00"; + sExpected = "271433.61"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // Three decimals. + sCode = "[SS].000"; + sExpected = "271433.605"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#156449 Use '?' in exponent of scientific number + sCode = "0.00E+?0"; + sExpected = "3.14E+ 0"; // before change it was "3.14E+00" + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // There should be at least one '0' in exponent + sCode = "0.00E+??"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#33689 use English NfKeywords in non-English language + eLang = LANGUAGE_DUTCH; + sExpected = "Dutch: 1900/01/02 03:23:53"; + sCode = "\"Dutch:\" JJJJ/MM/DD UU:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Dutch: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_GERMAN; + sExpected = "German: 1900/01/02 03:23:53"; + sCode = "\"German: \"JJJJ/MM/TT HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"German: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_FRENCH; + sExpected = "French: 1900/01/02 03:23:53"; + sCode = "\"French: \"AAAA/MM/JJ HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"French: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_ITALIAN; + sExpected = "Italian: 1900/01/02 03:23:53"; + sCode = "\"Italian: \"AAAA/MM/GG HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Italian: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_PORTUGUESE; + sExpected = "Portuguese: 1900/01/02 03:23:53"; + sCode = "\"Portuguese: \"AAAA/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Portuguese: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_SPANISH_MODERN; + sExpected = "Spanish: 1900/01/02 03:23:53"; + sCode = "\"Spanish: \"AAAA/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Spanish: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_DANISH; + sExpected = "Danish: 1900/01/02 03:23:53"; + sCode = "\"Danish: \"YYYY/MM/DD TT:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Danish: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + eLang = LANGUAGE_FINNISH; + sExpected = "Finnish: 1900/01/02 03:23:53"; + sCode = "\"Finnish: \"VVVV/KK/PP TT:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + sCode = "\"Finnish: \"YYYY/MM/DD HH:MM:SS"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } + { // tdf#117819 wrong separator positions when displaying integers with + // more decimals than rtl::math::doubleToUString delivers. + sCode = "#,##0.00000000000000000000"; + sExpected = "117,669,030,460,994.00000000000000000000"; + checkPreviewString(aFormatter, sCode, 117669030460994.0, LANGUAGE_ENGLISH_US, sExpected); + } + { // tdf#117575 treat thousand separator with '?' in integer part + sCode = "\"Value= \"?,??0.00"; + sExpected = "Value= 3.14"; + checkPreviewString(aFormatter, sCode, M_PI, LANGUAGE_ENGLISH_US, sExpected); + sExpected = "Value= 12.00"; + checkPreviewString(aFormatter, sCode, 12, LANGUAGE_ENGLISH_US, sExpected); + sExpected = "Value= 123.00"; + checkPreviewString(aFormatter, sCode, 123, LANGUAGE_ENGLISH_US, sExpected); + sExpected = "Value= 1,234.00"; + checkPreviewString(aFormatter, sCode, 1234, LANGUAGE_ENGLISH_US, sExpected); + sExpected = "Value= 12,345.00"; + checkPreviewString(aFormatter, sCode, 12345, LANGUAGE_ENGLISH_US, sExpected); + } +} + +void Test::testNfEnglishKeywordsIntegrity() +{ + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_ENGLISH_US); + const NfKeywordTable& rEnglishKeywords = aFormatter.GetEnglishKeywords(); + const NfKeywordTable& sKeywords = aFormatter.GetKeywords(0); + CPPUNIT_ASSERT_EQUAL( size_t(NF_KEYWORD_ENTRIES_COUNT), rEnglishKeywords.size() ); + for (size_t i = 0; i < size_t(NF_KEYWORD_ENTRIES_COUNT); ++i) + { + CPPUNIT_ASSERT_EQUAL( sKeywords[i], rEnglishKeywords[i] ); + } + // Check the order of sEnglishKeyword + CPPUNIT_ASSERT_EQUAL( OUString("E"), rEnglishKeywords[NF_KEY_E] ); + CPPUNIT_ASSERT_EQUAL( OUString("AM/PM"), rEnglishKeywords[NF_KEY_AMPM] ); + CPPUNIT_ASSERT_EQUAL( OUString("A/P"), rEnglishKeywords[NF_KEY_AP] ); + CPPUNIT_ASSERT_EQUAL( OUString("M"), rEnglishKeywords[NF_KEY_MI] ); + CPPUNIT_ASSERT_EQUAL( OUString("MM"), rEnglishKeywords[NF_KEY_MMI] ); + CPPUNIT_ASSERT_EQUAL( OUString("M"), rEnglishKeywords[NF_KEY_M] ); + CPPUNIT_ASSERT_EQUAL( OUString("MM"), rEnglishKeywords[NF_KEY_MM] ); + CPPUNIT_ASSERT_EQUAL( OUString("MMM"), rEnglishKeywords[NF_KEY_MMM] ); + CPPUNIT_ASSERT_EQUAL( OUString("MMMM"), rEnglishKeywords[NF_KEY_MMMM] ); + CPPUNIT_ASSERT_EQUAL( OUString("H"), rEnglishKeywords[NF_KEY_H] ); + CPPUNIT_ASSERT_EQUAL( OUString("HH"), rEnglishKeywords[NF_KEY_HH] ); + CPPUNIT_ASSERT_EQUAL( OUString("S"), rEnglishKeywords[NF_KEY_S] ); + CPPUNIT_ASSERT_EQUAL( OUString("SS"), rEnglishKeywords[NF_KEY_SS] ); + CPPUNIT_ASSERT_EQUAL( OUString("Q"), rEnglishKeywords[NF_KEY_Q] ); + CPPUNIT_ASSERT_EQUAL( OUString("QQ"), rEnglishKeywords[NF_KEY_QQ] ); + CPPUNIT_ASSERT_EQUAL( OUString("D"), rEnglishKeywords[NF_KEY_D] ); + CPPUNIT_ASSERT_EQUAL( OUString("DD"), rEnglishKeywords[NF_KEY_DD] ); + CPPUNIT_ASSERT_EQUAL( OUString("DDD"), rEnglishKeywords[NF_KEY_DDD] ); + CPPUNIT_ASSERT_EQUAL( OUString("DDDD"), rEnglishKeywords[NF_KEY_DDDD] ); + CPPUNIT_ASSERT_EQUAL( OUString("YY"), rEnglishKeywords[NF_KEY_YY] ); + CPPUNIT_ASSERT_EQUAL( OUString("YYYY"), rEnglishKeywords[NF_KEY_YYYY] ); + CPPUNIT_ASSERT_EQUAL( OUString("NN"), rEnglishKeywords[NF_KEY_NN] ); + CPPUNIT_ASSERT_EQUAL( OUString("NNNN"), rEnglishKeywords[NF_KEY_NNNN] ); + CPPUNIT_ASSERT_EQUAL( OUString("CCC"), rEnglishKeywords[NF_KEY_CCC] ); + CPPUNIT_ASSERT_EQUAL( OUString("GENERAL"), rEnglishKeywords[NF_KEY_GENERAL] ); + CPPUNIT_ASSERT_EQUAL( OUString("NNN"), rEnglishKeywords[NF_KEY_NNN] ); + CPPUNIT_ASSERT_EQUAL( OUString("WW"), rEnglishKeywords[NF_KEY_WW] ); + CPPUNIT_ASSERT_EQUAL( OUString("MMMMM"), rEnglishKeywords[NF_KEY_MMMMM] ); + CPPUNIT_ASSERT_EQUAL( OUString("TRUE"), rEnglishKeywords[NF_KEY_TRUE] ); + CPPUNIT_ASSERT_EQUAL( OUString("FALSE"), rEnglishKeywords[NF_KEY_FALSE] ); + CPPUNIT_ASSERT_EQUAL( OUString("BOOLEAN"), rEnglishKeywords[NF_KEY_BOOLEAN] ); + CPPUNIT_ASSERT_EQUAL( OUString("COLOR"), rEnglishKeywords[NF_KEY_COLOR] ); + CPPUNIT_ASSERT_EQUAL( OUString("BLACK"), rEnglishKeywords[NF_KEY_BLACK] ); + CPPUNIT_ASSERT_EQUAL( OUString("BLUE"), rEnglishKeywords[NF_KEY_BLUE] ); + CPPUNIT_ASSERT_EQUAL( OUString("GREEN"), rEnglishKeywords[NF_KEY_GREEN] ); + CPPUNIT_ASSERT_EQUAL( OUString("CYAN"), rEnglishKeywords[NF_KEY_CYAN] ); + CPPUNIT_ASSERT_EQUAL( OUString("RED"), rEnglishKeywords[NF_KEY_RED] ); + CPPUNIT_ASSERT_EQUAL( OUString("MAGENTA"), rEnglishKeywords[NF_KEY_MAGENTA] ); + CPPUNIT_ASSERT_EQUAL( OUString("BROWN"), rEnglishKeywords[NF_KEY_BROWN] ); + CPPUNIT_ASSERT_EQUAL( OUString("GREY"), rEnglishKeywords[NF_KEY_GREY] ); + CPPUNIT_ASSERT_EQUAL( OUString("YELLOW"), rEnglishKeywords[NF_KEY_YELLOW] ); + CPPUNIT_ASSERT_EQUAL( OUString("WHITE"), rEnglishKeywords[NF_KEY_WHITE] ); + CPPUNIT_ASSERT_EQUAL( OUString("AAA"), rEnglishKeywords[NF_KEY_AAA]); + CPPUNIT_ASSERT_EQUAL( OUString("AAAA"), rEnglishKeywords[NF_KEY_AAAA] ); + CPPUNIT_ASSERT_EQUAL( OUString("E"), rEnglishKeywords[NF_KEY_EC] ); + CPPUNIT_ASSERT_EQUAL( OUString("EE"), rEnglishKeywords[NF_KEY_EEC] ); + CPPUNIT_ASSERT_EQUAL( OUString("G"), rEnglishKeywords[NF_KEY_G] ); + CPPUNIT_ASSERT_EQUAL( OUString("GG"), rEnglishKeywords[NF_KEY_GG] ); + CPPUNIT_ASSERT_EQUAL( OUString("GGG"), rEnglishKeywords[NF_KEY_GGG] ); + CPPUNIT_ASSERT_EQUAL( OUString("R"), rEnglishKeywords[NF_KEY_R] ); + CPPUNIT_ASSERT_EQUAL( OUString("RR"), rEnglishKeywords[NF_KEY_RR] ); + CPPUNIT_ASSERT_EQUAL( OUString("t"), rEnglishKeywords[NF_KEY_THAI_T] ); +} + +void Test::testStandardColorIntegrity() +{ + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_ENGLISH_US); + const ::std::vector<Color> & rStandardColors = aFormatter.GetStandardColors(); + const size_t nMaxDefaultColors = aFormatter.GetMaxDefaultColors(); + CPPUNIT_ASSERT_EQUAL( size_t(NF_KEY_LASTCOLOR) - size_t(NF_KEY_FIRSTCOLOR) + 1, nMaxDefaultColors ); + CPPUNIT_ASSERT_EQUAL( nMaxDefaultColors, rStandardColors.size() ); + // Colors must follow same order as in sEnglishKeyword + CPPUNIT_ASSERT_EQUAL( COL_BLACK, rStandardColors[0] ); + CPPUNIT_ASSERT_EQUAL( COL_LIGHTBLUE, rStandardColors[1] ); + CPPUNIT_ASSERT_EQUAL( COL_LIGHTGREEN, rStandardColors[2] ); + CPPUNIT_ASSERT_EQUAL( COL_LIGHTCYAN, rStandardColors[3] ); + CPPUNIT_ASSERT_EQUAL( COL_LIGHTRED, rStandardColors[4] ); + CPPUNIT_ASSERT_EQUAL( COL_LIGHTMAGENTA, rStandardColors[5] ); + CPPUNIT_ASSERT_EQUAL( COL_BROWN, rStandardColors[6] ); + CPPUNIT_ASSERT_EQUAL( COL_GRAY, rStandardColors[7] ); + CPPUNIT_ASSERT_EQUAL( COL_YELLOW, rStandardColors[8] ); + CPPUNIT_ASSERT_EQUAL( COL_WHITE, rStandardColors[9] ); +} + +void Test::testColorNamesConversion() +{ + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_GERMAN); + const NfKeywordTable& rEnglishKeywords = aFormatter.GetEnglishKeywords(); + const NfKeywordTable& rKeywords = aFormatter.GetKeywords(0); + + // Holding a reference to the NfKeywordTable doesn't help if we switch + // locales internally, so copy the relevant parts in advance. + std::vector<OUString> aGermanKeywords(NF_KEYWORD_ENTRIES_COUNT); + for (size_t i = NF_KEY_COLOR; i <= NF_KEY_WHITE; ++i) + aGermanKeywords[i] = rKeywords[i]; + + // Check that we actually have German and English keywords. + CPPUNIT_ASSERT_EQUAL( OUString("FARBE"), aGermanKeywords[NF_KEY_COLOR]); + CPPUNIT_ASSERT_EQUAL( OUString("COLOR"), rEnglishKeywords[NF_KEY_COLOR]); + + // Test each color conversion. + // [FARBE1] -> [COLOR1] can't be tested because we have no color table link + // set, so the scanner returns nCheckPos error. + sal_Int32 nCheckPos; + SvNumFormatType nType; + sal_uInt32 nKey; + OUString aFormatCode; + + for (size_t i = NF_KEY_BLACK; i <= NF_KEY_WHITE; ++i) + { + aFormatCode = "[" + aGermanKeywords[i] + "]0"; + aFormatter.PutandConvertEntry( aFormatCode, nCheckPos, nType, nKey, LANGUAGE_GERMAN, LANGUAGE_ENGLISH_US, false); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CheckPos should be 0.", sal_Int32(0), nCheckPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Type should be NUMBER.", SvNumFormatType::NUMBER, nType); + CPPUNIT_ASSERT_EQUAL( OUString("[" + rEnglishKeywords[i] + "]0"), aFormatCode); + } +} + +void Test::testExcelExportFormats() +{ + // Create a formatter with "system" locale other than the specific formats' + // locale, and different from the en-US export locale. + SvNumberFormatter aFormatter( m_xContext, LANGUAGE_ENGLISH_UK); + + OUString aCode; + sal_Int32 nCheckPos; + SvNumFormatType eType; + sal_uInt32 nKey1, nKey2; + + aCode = "00.00"; + aFormatter.PutandConvertEntry( aCode, nCheckPos, eType, nKey1, + LANGUAGE_ENGLISH_US, LANGUAGE_ENGLISH_SAFRICA, false); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CheckPos should be 0.", sal_Int32(0), nCheckPos); + CPPUNIT_ASSERT_MESSAGE("Key should be greater than system locale's keys.", + nKey1 > SV_COUNTRY_LANGUAGE_OFFSET); + + aCode = "[$R-1C09] #,##0.0;[$R-1C09]-#,##0.0"; + aFormatter.PutandConvertEntry( aCode, nCheckPos, eType, nKey2, + LANGUAGE_ENGLISH_US, LANGUAGE_ENGLISH_SAFRICA, false); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CheckPos should be 0.", sal_Int32(0), nCheckPos); + CPPUNIT_ASSERT_MESSAGE("Key should be greater than system locale's keys.", + nKey2 > SV_COUNTRY_LANGUAGE_OFFSET); + + // The export formatter. + SvNumberFormatter aTempFormatter( m_xContext, LANGUAGE_ENGLISH_US); + NfKeywordTable aKeywords; + aTempFormatter.FillKeywordTableForExcel( aKeywords); + + aCode = aFormatter.GetFormatStringForExcel( nKey1, aKeywords, aTempFormatter); + // Test that LCID is prepended. + CPPUNIT_ASSERT_EQUAL( OUString("[$-1C09]00.00"), aCode); + + aCode = aFormatter.GetFormatStringForExcel( nKey2, aKeywords, aTempFormatter); + // Test that LCID is not prepended. Note that literal characters are escaped. + CPPUNIT_ASSERT_EQUAL( OUString("[$R-1C09]\\ #,##0.0;[$R-1C09]\\-#,##0.0"), aCode); +} + +CPPUNIT_TEST_FIXTURE(Test, testLanguageNone) +{ + SvNumberFormatter aFormatter(m_xContext, LANGUAGE_ENGLISH_US); + NfKeywordTable keywords; + aFormatter.FillKeywordTableForExcel(keywords); + OUString code("TT.MM.JJJJ"); + sal_uInt32 nKey = aFormatter.GetEntryKey(code, LANGUAGE_GERMAN); + CPPUNIT_ASSERT(nKey != NUMBERFORMAT_ENTRY_NOT_FOUND); + SvNumberformat const*const pFormat = aFormatter.GetEntry(nKey); + LocaleDataWrapper ldw(m_xContext, LanguageTag(pFormat->GetLanguage())); + CPPUNIT_ASSERT_EQUAL(OUString("dd.mm.yyyy"), pFormat->GetMappedFormatstring(keywords, ldw)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/qa/unit/test_INetContentType.cxx b/svl/qa/unit/test_INetContentType.cxx new file mode 100644 index 0000000000..288cfe9190 --- /dev/null +++ b/svl/qa/unit/test_INetContentType.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <cstring> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> +#include <svl/inettype.hxx> +#include <tools/inetmime.hxx> + +namespace { + +class Test: public CppUnit::TestFixture { +public: + void testBad(); + + void testFull(); + + void testFollow(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testBad); + CPPUNIT_TEST(testFull); + CPPUNIT_TEST(testFollow); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testBad() { + OUString in("foo=bar"); + CPPUNIT_ASSERT_EQUAL( + static_cast<void const *>(nullptr), + static_cast<void const *>(INetMIME::scanContentType(in))); + OUString t; + OUString s; + INetContentTypeParameterList ps; + CPPUNIT_ASSERT(!INetContentTypes::parse(in, t, s, &ps)); + CPPUNIT_ASSERT(t.isEmpty()); + CPPUNIT_ASSERT(s.isEmpty()); + CPPUNIT_ASSERT(bool(ps.end() == ps.find("foo"_ostr))); +} + +void Test::testFull() { + OUString in("foo/bar;baz=boz"); + CPPUNIT_ASSERT_EQUAL( + static_cast<void const *>(in.getStr() + in.getLength()), + static_cast<void const *>(INetMIME::scanContentType(in))); + OUString t; + OUString s; + INetContentTypeParameterList ps; + CPPUNIT_ASSERT(INetContentTypes::parse(in, t, s, &ps)); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), t); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), s); + auto iter = ps.find("baz"_ostr); + CPPUNIT_ASSERT(iter != ps.end()); + CPPUNIT_ASSERT_EQUAL(OUString("boz"), iter->second.m_sValue); +} + +void Test::testFollow() { + OUString in("foo/bar;baz=boz;base64,"); + CPPUNIT_ASSERT_EQUAL( + static_cast<void const *>(in.getStr() + std::strlen("foo/bar;baz=boz")), + static_cast<void const *>(INetMIME::scanContentType(in))); + OUString t; + OUString s; + INetContentTypeParameterList ps; + CPPUNIT_ASSERT(!INetContentTypes::parse(in, t, s)); + CPPUNIT_ASSERT(t.isEmpty()); + CPPUNIT_ASSERT(s.isEmpty()); + CPPUNIT_ASSERT(bool(ps.end() == ps.find("baz"_ostr))); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/qa/unit/test_SvAddressParser.cxx b/svl/qa/unit/test_SvAddressParser.cxx new file mode 100644 index 0000000000..b015f9a1b3 --- /dev/null +++ b/svl/qa/unit/test_SvAddressParser.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <sal/types.h> +#include <svl/adrparse.hxx> + +namespace +{ +class Test : public CppUnit::TestFixture +{ + void testRfc822ExampleAddresses() + { + // Examples taken from section A.1 "Examples: Addresses" of + // <https://tools.ietf.org/html/rfc822> "Standard for the Format of ARPA Internet Text + // Messages": + { + SvAddressParser p("Alfred Neuman <Neuman@BBN-TENEXA>"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), p.Count()); + CPPUNIT_ASSERT_EQUAL(OUString("Neuman@BBN-TENEXA"), p.GetEmailAddress(0)); + } + { + SvAddressParser p("Neuman@BBN-TENEXA"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), p.Count()); + CPPUNIT_ASSERT_EQUAL(OUString("Neuman@BBN-TENEXA"), p.GetEmailAddress(0)); + } + { + SvAddressParser p("\"George, Ted\" <Shared@Group.Arpanet>"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), p.Count()); + CPPUNIT_ASSERT_EQUAL(OUString("Shared@Group.Arpanet"), p.GetEmailAddress(0)); + } + { + SvAddressParser p("Wilt . (the Stilt) Chamberlain@NBA.US"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), p.Count()); + CPPUNIT_ASSERT_EQUAL(OUString("Wilt.Chamberlain@NBA.US"), p.GetEmailAddress(0)); + } + { + SvAddressParser p("Gourmets: Pompous Person <WhoZiWhatZit@Cordon-Bleu>,\n" + " Childs@WGBH.Boston, Galloping Gourmet@\n" + " ANT.Down-Under (Australian National Television),\n" + " Cheapie@Discount-Liquors;,\n" + " Cruisers: Port@Portugal, Jones@SEA;,\n" + " Another@Somewhere.SomeOrg"); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), p.Count()); + CPPUNIT_ASSERT_EQUAL(OUString("WhoZiWhatZit@Cordon-Bleu"), p.GetEmailAddress(0)); + CPPUNIT_ASSERT_EQUAL(OUString("Childs@WGBH.Boston"), p.GetEmailAddress(1)); + CPPUNIT_ASSERT_EQUAL(OUString("Gourmet@ANT.Down-Under"), p.GetEmailAddress(2)); + CPPUNIT_ASSERT_EQUAL(OUString("Cheapie@Discount-Liquors"), p.GetEmailAddress(3)); + CPPUNIT_ASSERT_EQUAL(OUString("Port@Portugal"), p.GetEmailAddress(4)); + CPPUNIT_ASSERT_EQUAL(OUString("Jones@SEA"), p.GetEmailAddress(5)); + CPPUNIT_ASSERT_EQUAL(OUString("Another@Somewhere.SomeOrg"), p.GetEmailAddress(6)); + } + } + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testRfc822ExampleAddresses); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svl/qa/unit/test_URIHelper.cxx b/svl/qa/unit/test_URIHelper.cxx new file mode 100644 index 0000000000..516f4eb4fd --- /dev/null +++ b/svl/qa/unit/test_URIHelper.cxx @@ -0,0 +1,528 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> +#include <cstddef> + +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/ucb/Command.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XCommandProcessor.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/ucb/XContentProvider.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <cppuhelper/bootstrap.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/strbuf.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <sal/macros.h> +#include <sal/types.h> +#include <svl/urihelper.hxx> +#include <unotools/charclass.hxx> + +namespace com::sun::star::ucb { + class XCommandEnvironment; + class XContentEventListener; +} + +namespace { + +// This class only implements that subset of functionality of a proper +// css::ucb::Content that is known to be needed here: +class Content: + public cppu::WeakImplHelper< + css::ucb::XContent, css::ucb::XCommandProcessor > +{ +public: + explicit Content( + css::uno::Reference< css::ucb::XContentIdentifier > const & identifier); + + virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL + getIdentifier() override { + return m_identifier; + } + + virtual OUString SAL_CALL getContentType() override + { + return OUString(); + } + + virtual void SAL_CALL addContentEventListener( + css::uno::Reference< css::ucb::XContentEventListener > const &) override + {} + + virtual void SAL_CALL removeContentEventListener( + css::uno::Reference< css::ucb::XContentEventListener > const &) override + {} + + virtual sal_Int32 SAL_CALL createCommandIdentifier() override + { + return 0; + } + + virtual css::uno::Any SAL_CALL execute( + css::ucb::Command const & command, sal_Int32 commandId, + css::uno::Reference< css::ucb::XCommandEnvironment > const &) override; + + virtual void SAL_CALL abort(sal_Int32) override {} + +private: + static char const m_prefix[]; + + css::uno::Reference< css::ucb::XContentIdentifier > m_identifier; +}; + +char const Content::m_prefix[] = "test:"; + +Content::Content( + css::uno::Reference< css::ucb::XContentIdentifier > const & identifier): + m_identifier(identifier) +{ + assert(m_identifier.is()); + OUString uri(m_identifier->getContentIdentifier()); + if (!uri.matchIgnoreAsciiCase(m_prefix) + || uri.indexOf('#', RTL_CONSTASCII_LENGTH(m_prefix)) != -1) + { + throw css::ucb::IllegalIdentifierException(); + } +} + +css::uno::Any Content::execute( + css::ucb::Command const & command, sal_Int32, + css::uno::Reference< css::ucb::XCommandEnvironment > const &) +{ + if ( command.Name != "getCasePreservingURL" ) + { + throw css::uno::RuntimeException(); + } + // If any non-empty segment starts with anything but '0', '1', or '2', fail; + // otherwise, if the last non-empty segment starts with '1', add a final + // slash, and if the last non-empty segment starts with '2', remove a final + // slash (if any); also, turn the given uri into all-lowercase: + OUString uri(m_identifier->getContentIdentifier()); + sal_Unicode c = '0'; + for (sal_Int32 i = RTL_CONSTASCII_LENGTH(m_prefix); i != -1;) { + OUString seg(uri.getToken(0, '/', i)); + if (seg.getLength() > 0) { + c = seg[0]; + if (c < '0' || c > '2') { + throw css::uno::Exception(); + } + } + } + switch (c) { + case '1': + uri += "/"; + break; + case '2': + if (uri.endsWith("/")) { + uri = uri.copy(0, uri.getLength() -1); + } + break; + } + return css::uno::Any(uri.toAsciiLowerCase()); +} + +class Provider: public cppu::WeakImplHelper< css::ucb::XContentProvider > { +public: + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL queryContent( + css::uno::Reference< css::ucb::XContentIdentifier > const & identifier) override + { + return new Content(identifier); + } + + virtual sal_Int32 SAL_CALL compareContentIds( + css::uno::Reference< css::ucb::XContentIdentifier > const & id1, + css::uno::Reference< css::ucb::XContentIdentifier > const & id2) override + { + assert(id1.is() && id2.is()); + return + id1->getContentIdentifier().compareTo(id2->getContentIdentifier()); + } +}; + +class Test: public CppUnit::TestFixture { +public: + virtual void setUp() override; + + void finish(); + + void testNormalizedMakeRelative(); + + void testFindFirstURLInText(); + + void testFindFirstDOIInText(); + + void testResolveIdnaHost(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testNormalizedMakeRelative); + CPPUNIT_TEST(testFindFirstURLInText); + CPPUNIT_TEST(testFindFirstDOIInText); + CPPUNIT_TEST(testResolveIdnaHost); + CPPUNIT_TEST(finish); + CPPUNIT_TEST_SUITE_END(); + +private: + static css::uno::Reference< css::uno::XComponentContext > m_context; +}; + +void Test::setUp() { + // For whatever reason, on W32 it does not work to create/destroy a fresh + // component context for each test in Test::setUp/tearDown; therefore, a + // single component context is used for all tests and destroyed in the last + // pseudo-test "finish": + if (!m_context.is()) { + m_context = cppu::defaultBootstrap_InitialComponentContext(); + } +} + +void Test::finish() { + css::uno::Reference< css::lang::XComponent >( + m_context, css::uno::UNO_QUERY_THROW)->dispose(); +} + +void Test::testNormalizedMakeRelative() { + auto ucb(css::ucb::UniversalContentBroker::create(m_context)); + ucb->registerContentProvider(new Provider, "test", true); + ucb->registerContentProvider( + css::uno::Reference<css::ucb::XContentProvider>( + m_context->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.ucb.FileProvider", m_context), + css::uno::UNO_QUERY_THROW), + "file", true); + struct Data { + char const * base; + char const * absolute; + char const * relative; + }; + static Data const tests[] = { + { "hierarchical:/", "mailto:def@a.b.c.", "mailto:def@a.b.c." }, + { "hierarchical:/", "a/b/c", "a/b/c" }, + { "hierarchical:/a", "hierarchical:/a/b/c?d#e", "/a/b/c?d#e" }, + { "hierarchical:/a/", "hierarchical:/a/b/c?d#e", "b/c?d#e" }, + { "test:/0/0/a", "test:/0/b", "../b" }, + { "test:/1/1/a", "test:/1/b", "../b" }, + { "test:/2/2//a", "test:/2/b", "../../b" }, + { "test:/0a/b", "test:/0A/c#f", "c#f" }, + { "file:///usr/bin/nonex1/nonex2", + "file:///usr/bin/nonex1/nonex3/nonex4", "nonex3/nonex4" }, + { "file:///usr/bin/nonex1/nonex2#fragmentA", + "file:///usr/bin/nonex1/nonex3/nonex4#fragmentB", + "nonex3/nonex4#fragmentB" }, + { "file:///usr/nonex1/nonex2", "file:///usr/nonex3", "../nonex3" }, + { "file:///c:/windows/nonex1", "file:///c:/nonex2", "../nonex2" }, +#if defined(_WIN32) + { "file:///c:/nonex1/nonex2", "file:///C:/nonex1/nonex3/nonex4", + "nonex3/nonex4" } +#endif + }; + for (auto const[base, absolute, relative] : tests) + { + css::uno::Reference< css::uri::XUriReference > ref(URIHelper::normalizedMakeRelative( + m_context, OUString::createFromAscii(base), OUString::createFromAscii(absolute))); + bool ok = relative == nullptr ? !ref.is() + : ref.is() && ref->getUriReference().equalsAscii(relative); + OString msg; + if (!ok) + { + OStringBuffer buf(OString::Concat("<") + base + ">, <" + absolute + ">: "); + if (ref.is()) + { + buf.append('<'); + buf.append( + OUStringToOString( + ref->getUriReference(), RTL_TEXTENCODING_UTF8)); + buf.append('>'); + } + else + { + buf.append("none"); + } + buf.append(" instead of "); + if (relative == nullptr) + { + buf.append("none"); + } + else + { + buf.append(OString::Concat("<") + relative + ">"); + } + msg = buf.makeStringAndClear(); + } + CPPUNIT_ASSERT_MESSAGE(msg.getStr(), ok); + } +} + +void Test::testFindFirstURLInText() { + struct Data { + char const * input; + char const * result; + sal_Int32 begin; + sal_Int32 end; + }; + static Data const tests[] = { + { "...ftp://bla.bla.bla/blubber/...", + "ftp://bla.bla.bla/blubber/", 3, 29 }, + { "..\\ftp://bla.bla.bla/blubber/...", nullptr, 0, 0 }, + { "..\\ftp:\\\\bla.bla.bla\\blubber/...", + "file://bla.bla.bla/blubber%2F", 7, 29 }, + { "http://sun.com", "http://sun.com/", 0, 14 }, + { "http://sun.com/", "http://sun.com/", 0, 15 }, + { "http://www.xerox.com@www.pcworld.com/go/3990332.htm", nullptr, 0, 0 }, + { "ftp://www.xerox.com@www.pcworld.com/go/3990332.htm", + "ftp://www.xerox.com@www.pcworld.com/go/3990332.htm", 0, 50 }, + { "Version.1.2.3", nullptr, 0, 0 }, + { "Version:1.2.3", nullptr, 0, 0 }, + { "a.b.c", nullptr, 0, 0 }, + { "file:///a|...", "file:///a:", 0, 10 }, + { "file:///a||...", "file:///a%7C%7C", 0, 11 }, + { "file:///a|/bc#...", "file:///a:/bc", 0, 13 }, + { "file:///a|/bc#de...", "file:///a:/bc#de", 0, 16 }, + { "abc.def.ghi,ftp.xxx.yyy/zzz...", "ftp://ftp.xxx.yyy/zzz", 12, 27 }, + { "abc.def.ghi,Ftp.xxx.yyy/zzz...", "ftp://Ftp.xxx.yyy/zzz", 12, 27 }, + { "abc.def.ghi,www.xxx.yyy...", "http://www.xxx.yyy/", 12, 23 }, + { "abc.def.ghi,wwww.xxx.yyy...", nullptr, 0, 0 }, + { "abc.def.ghi,wWW.xxx.yyy...", "http://wWW.xxx.yyy/", 12, 23 }, + { "Bla {mailto.me@abc.def.g.h.i}...", + "mailto:%7Bmailto.me@abc.def.g.h.i", 4, 28 }, + { "abc@def@ghi", nullptr, 0, 0 }, + { "lala@sun.com", "mailto:lala@sun.com", 0, 12 }, + { "1lala@sun.com", "mailto:1lala@sun.com", 0, 13 }, + { "aaa_bbb@xxx.yy", "mailto:aaa_bbb@xxx.yy", 0, 14 }, + { "{a:\\bla/bla/bla...}", "file:///a:/bla/bla/bla", 1, 15 }, + { "#b:/c/d#e#f#", "file:///b:/c/d", 1, 7 }, + { "a:/", "file:///a:/", 0, 3 }, + { "http://sun.com/R_(l_a)", "http://sun.com/R_(l_a)", 0, 22 }, + { ".component:", nullptr, 0, 0 }, + { ".uno:", nullptr, 0, 0 }, + { "cid:", nullptr, 0, 0 }, + { "data:", nullptr, 0, 0 }, + { "db:", nullptr, 0, 0 }, + { "file:", nullptr, 0, 0 }, + { "ftp:", nullptr, 0, 0 }, + { "http:", nullptr, 0, 0 }, + { "https:", nullptr, 0, 0 }, + { "imap:", nullptr, 0, 0 }, + { "javascript:", nullptr, 0, 0 }, + { "ldap:", nullptr, 0, 0 }, + { "macro:", nullptr, 0, 0 }, + { "mailto:", nullptr, 0, 0 }, + { "news:", nullptr, 0, 0 }, + { "out:", nullptr, 0, 0 }, + { "pop3:", nullptr, 0, 0 }, + { "private:", nullptr, 0, 0 }, + { "slot:", nullptr, 0, 0 }, + { "staroffice.component:", nullptr, 0, 0 }, + { "staroffice.db:", nullptr, 0, 0 }, + { "staroffice.factory:", nullptr, 0, 0 }, + { "staroffice.helpid:", nullptr, 0, 0 }, + { "staroffice.java:", nullptr, 0, 0 }, + { "staroffice.macro:", nullptr, 0, 0 }, + { "staroffice.out:", nullptr, 0, 0 }, + { "staroffice.pop3:", nullptr, 0, 0 }, + { "staroffice.private:", nullptr, 0, 0 }, + { "staroffice.searchfolder:", nullptr, 0, 0 }, + { "staroffice.slot:", nullptr, 0, 0 }, + { "staroffice.trashcan:", nullptr, 0, 0 }, + { "staroffice.uno:", nullptr, 0, 0 }, + { "staroffice.vim:", nullptr, 0, 0 }, + { "staroffice:", nullptr, 0, 0 }, + { "vim:", nullptr, 0, 0 }, + { "vnd.sun.star.cmd:", nullptr, 0, 0 }, + { "vnd.sun.star.help:", nullptr, 0, 0 }, + { "vnd.sun.star.hier:", nullptr, 0, 0 }, + { "vnd.sun.star.pkg:", nullptr, 0, 0 }, + { "vnd.sun.star.script:", nullptr, 0, 0 }, + { "vnd.sun.star.webdav:", nullptr, 0, 0 }, + { "vnd.sun.star.wfs:", nullptr, 0, 0 }, + { "generic:path", nullptr, 0, 0 }, + { "wfs:", nullptr, 0, 0 } + }; + CharClass charClass( m_context, LanguageTag( css::lang::Locale("en", "US", ""))); + for (auto const[pInput, pResult, nBegin, nEnd] : tests) + { + OUString input(OUString::createFromAscii(pInput)); + sal_Int32 begin = 0; + sal_Int32 end = input.getLength(); + OUString result(URIHelper::FindFirstURLInText(input, begin, end, charClass)); + bool ok = pResult == nullptr + ? (result.getLength() == 0 && begin == input.getLength() + && end == input.getLength()) + : (result.equalsAscii(pResult) && begin == nBegin && end == nEnd); + OString msg; + if (!ok) + { + OStringBuffer buf; + buf.append(OString::Concat("\"") + pInput + "\" -> "); + buf.append(pResult == nullptr ? "none" : pResult); + buf.append(" (" + OString::number(nBegin) + ", " + OString::number(nEnd) + + ")" + " != " + + OUStringToOString(result, RTL_TEXTENCODING_UTF8) + " (" + + OString::number(begin) + ", " + OString::number(end) +")"); + msg = buf.makeStringAndClear(); + } + CPPUNIT_ASSERT_MESSAGE(msg.getStr(), ok); + } +} + +void Test::testFindFirstDOIInText() { + struct Data { + char const * input; + char const * result; + sal_Int32 begin; + sal_Int32 end; + }; + static Data const tests[] = { + { "doi:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with only digits + { "Doi:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with some of the first three characters being capitalized + { "DoI:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with some of the first three characters being capitalized + { "DOI:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with some of the first three characters being capitalized + { "dOI:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with some of the first three characters being capitalized + { "dOi:10.1000/182", "https://doi.org/10.1000/182", 0, 15 }, // valid doi suffix with some of the first three characters being capitalized + { "doi:10.1038/nature03001", "https://doi.org/10.1038/nature03001", 0, 23 }, // valid doi suffix with alphanumeric characters + { "doi:10.1093/ajae/aaq063", "https://doi.org/10.1093/ajae/aaq063", 0, 23 }, // valid doi suffix with multiple slash + { "doi:10.1016/S0735-1097(98)00347-7", "https://doi.org/10.1016/S0735-1097(98)00347-7", 0, 33 }, // valid doi suffix with characters apart from alphanumeric + { "doi:10.109/ajae/aaq063", nullptr, 0, 0 }, // # of digits after doi;10. is not between 4 and 9 + { "doi:10.1234567890/ajae/aaq063", nullptr, 0, 0 }, // # of digits after doi;10. is not between 4 and 9 + { "doi:10.1093/ajae/aaq063/", nullptr, 0, 0 }, // nothing after slash + { "doi:10.1093", nullptr, 0, 0 }, // no slash + { "doi:11.1093/ajae/aaq063", nullptr, 0, 0 }, // doesn't begin with doi:10. + }; + CharClass charClass( m_context, LanguageTag( css::lang::Locale("en", "US", ""))); + for (auto const[pInput, pResult, nBegin, nEnd] : tests) + { + OUString input(OUString::createFromAscii(pInput)); + sal_Int32 begin = 0; + sal_Int32 end = input.getLength(); + OUString result( + URIHelper::FindFirstDOIInText(input, begin, end, charClass)); + bool ok = pResult == nullptr + ? (result.getLength() == 0 && begin == input.getLength() && end == input.getLength()) + : (result.equalsAscii(pResult) && begin == nBegin && end == nEnd); + OString msg; + if (!ok) + { + OStringBuffer buf; + buf.append(OString::Concat("\"") + pInput + "\" -> "); + buf.append(pResult == nullptr ? "none" : pResult); + buf.append(" (" + OString::number(nBegin) + ", " + OString::number(nEnd) + + ")" + " != " + + OUStringToOString(result, RTL_TEXTENCODING_UTF8) + " (" + + OString::number(begin) + ", " + OString::number(end) +")"); + msg = buf.makeStringAndClear(); + } + CPPUNIT_ASSERT_MESSAGE(msg.getStr(), ok); + } +} + +void Test::testResolveIdnaHost() { + OUString input; + + input.clear(); + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"Foo.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = "foo://Muenchen.de"; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://-M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://M\u00FCnchen-.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://xn--M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://xy--M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://-bar.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://bar-.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://xn--bar.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + input = u"foo://xy--bar.M\u00FCnchen.de"_ustr; + CPPUNIT_ASSERT_EQUAL(input, URIHelper::resolveIdnaHost(input)); + + CPPUNIT_ASSERT_EQUAL( + u"foo://M\u00FCnchen@xn--mnchen-3ya.de"_ustr, + URIHelper::resolveIdnaHost(u"foo://M\u00FCnchen@M\u00FCnchen.de"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://xn--mnchen-3ya.de."), + URIHelper::resolveIdnaHost(u"foo://M\u00FCnchen.de."_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("Foo://bar@xn--mnchen-3ya.de:123/?bar#baz"), + URIHelper::resolveIdnaHost(u"Foo://bar@M\u00FCnchen.de:123/?bar#baz"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://xn--mnchen-3ya.de"), + URIHelper::resolveIdnaHost(u"foo://Mu\u0308nchen.de"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://example.xn--m-eha"), URIHelper::resolveIdnaHost(u"foo://example.mü"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://example.xn--m-eha:0"), URIHelper::resolveIdnaHost(u"foo://example.mü:0"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://xn--e1afmkfd.xn--p1ai"), URIHelper::resolveIdnaHost(u"foo://пример.рф"_ustr)); + + CPPUNIT_ASSERT_EQUAL( + OUString("foo://xn--e1afmkfd.xn--p1ai:0"), + URIHelper::resolveIdnaHost(u"foo://пример.рф:0"_ustr)); +} + +css::uno::Reference< css::uno::XComponentContext > Test::m_context; + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/qa/unit/test_lngmisc.cxx b/svl/qa/unit/test_lngmisc.cxx new file mode 100644 index 0000000000..2e82deac63 --- /dev/null +++ b/svl/qa/unit/test_lngmisc.cxx @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <o3tl/cppunittraitshelper.hxx> +#include <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <svl/lngmisc.hxx> + +#include <rtl/ustrbuf.hxx> + +namespace +{ +class LngMiscTest : public CppUnit::TestFixture +{ +private: + void testRemoveHyphens(); + void testRemoveControlChars(); + void testReplaceControlChars(); + void testGetThesaurusReplaceText(); + + CPPUNIT_TEST_SUITE(LngMiscTest); + + CPPUNIT_TEST(testRemoveHyphens); + CPPUNIT_TEST(testRemoveControlChars); + CPPUNIT_TEST(testReplaceControlChars); + CPPUNIT_TEST(testGetThesaurusReplaceText); + + CPPUNIT_TEST_SUITE_END(); +}; + +void LngMiscTest::testRemoveHyphens() +{ + OUString str1(""); + OUString str2("a-b--c---"); + + OUString str3 = OUStringChar(SVT_SOFT_HYPHEN) + OUStringChar(SVT_HARD_HYPHEN) + + OUStringChar(SVT_HARD_HYPHEN); + + OUString str4("asdf"); + + bool bModified = linguistic::RemoveHyphens(str1); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT(str1.isEmpty()); + + // Note that '-' isn't a hyphen to RemoveHyphens. + bModified = linguistic::RemoveHyphens(str2); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT_EQUAL(OUString("a-b--c---"), str2); + + bModified = linguistic::RemoveHyphens(str3); + CPPUNIT_ASSERT(bModified); + CPPUNIT_ASSERT(str3.isEmpty()); + + bModified = linguistic::RemoveHyphens(str4); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT_EQUAL(OUString("asdf"), str4); +} + +void LngMiscTest::testRemoveControlChars() +{ + OUString str1(""); + OUString str2("asdf"); + OUString str3("asdf\nasdf"); + + OUStringBuffer str4Buf(33); + str4Buf.setLength(33); + for (int i = 0; i < 33; i++) + str4Buf[i] = static_cast<sal_Unicode>(i); + // TODO: is this a bug? shouldn't RemoveControlChars remove this? + // str4Buf[33] = static_cast<sal_Unicode>(0x7F); + OUString str4(str4Buf.makeStringAndClear()); + + bool bModified = linguistic::RemoveControlChars(str1); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT(str1.isEmpty()); + + bModified = linguistic::RemoveControlChars(str2); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT_EQUAL(OUString("asdf"), str2); + + bModified = linguistic::RemoveControlChars(str3); + CPPUNIT_ASSERT(bModified); + CPPUNIT_ASSERT_EQUAL(OUString("asdfasdf"), str3); + + bModified = linguistic::RemoveControlChars(str4); + CPPUNIT_ASSERT(bModified); + CPPUNIT_ASSERT_EQUAL(OUString(" "), str4); +} + +void LngMiscTest::testReplaceControlChars() +{ + OUString str1(""); + OUString str2("asdf"); + OUString str3("asdf\nasdf"); + + OUStringBuffer str4Buf(33); + str4Buf.setLength(33); + for (int i = 0; i < 33; i++) + str4Buf[i] = static_cast<sal_Unicode>(i); + // TODO: is this a bug? shouldn't RemoveControlChars remove this? + // str4Buf[33] = static_cast<sal_Unicode>(0x7F); + OUString str4(str4Buf.makeStringAndClear()); + + bool bModified = linguistic::ReplaceControlChars(str1); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT(str1.isEmpty()); + + bModified = linguistic::ReplaceControlChars(str2); + CPPUNIT_ASSERT(!bModified); + CPPUNIT_ASSERT_EQUAL(OUString("asdf"), str2); + + bModified = linguistic::ReplaceControlChars(str3); + CPPUNIT_ASSERT(bModified); + CPPUNIT_ASSERT_EQUAL(OUString("asdf asdf"), str3); + + bModified = linguistic::ReplaceControlChars(str4); + CPPUNIT_ASSERT(bModified); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(32), str4.getLength()); + for (int i = 0; i < 32; i++) + CPPUNIT_ASSERT_EQUAL(u' ', str4[i]); +} + +void LngMiscTest::testGetThesaurusReplaceText() +{ + constexpr OUString str2(u"asdf"_ustr); + + OUString r = linguistic::GetThesaurusReplaceText(""); + CPPUNIT_ASSERT(r.isEmpty()); + + r = linguistic::GetThesaurusReplaceText(str2); + CPPUNIT_ASSERT_EQUAL(str2, r); + + r = linguistic::GetThesaurusReplaceText("asdf (abc)"); + CPPUNIT_ASSERT_EQUAL(str2, r); + + r = linguistic::GetThesaurusReplaceText("asdf*"); + CPPUNIT_ASSERT_EQUAL(str2, r); + + r = linguistic::GetThesaurusReplaceText("asdf * "); + CPPUNIT_ASSERT_EQUAL(str2, r); + + r = linguistic::GetThesaurusReplaceText("asdf (abc) *"); + CPPUNIT_ASSERT_EQUAL(str2, r); + + r = linguistic::GetThesaurusReplaceText("asdf asdf * (abc)"); + CPPUNIT_ASSERT_EQUAL(OUString("asdf asdf"), r); + + r = linguistic::GetThesaurusReplaceText(" * (abc) asdf *"); + CPPUNIT_ASSERT(r.isEmpty()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(LngMiscTest); +} +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |