summaryrefslogtreecommitdiffstats
path: root/svl/qa
diff options
context:
space:
mode:
Diffstat (limited to 'svl/qa')
-rw-r--r--svl/qa/complex/ConfigItems/CheckConfigItems.java156
-rw-r--r--svl/qa/complex/passwordcontainer/MasterPasswdHandler.java73
-rw-r--r--svl/qa/complex/passwordcontainer/PasswordContainerTest.java23
-rw-r--r--svl/qa/complex/passwordcontainer/PasswordContainerUnitTest.java84
-rw-r--r--svl/qa/complex/passwordcontainer/Test01.java98
-rw-r--r--svl/qa/complex/passwordcontainer/Test02.java144
-rw-r--r--svl/qa/complex/passwordcontainer/Test03.java108
-rw-r--r--svl/qa/complex/passwordcontainer/TestHelper.java78
-rw-r--r--svl/qa/unit/items/stylepool.cxx84
-rw-r--r--svl/qa/unit/items/test_IndexedStyleSheets.cxx210
-rw-r--r--svl/qa/unit/items/test_itempool.cxx108
-rw-r--r--svl/qa/unit/lockfiles/test_lockfiles.cxx706
-rw-r--r--svl/qa/unit/notify/test_SfxBroadcaster.cxx118
-rw-r--r--svl/qa/unit/svl.cxx2002
-rw-r--r--svl/qa/unit/test_INetContentType.cxx89
-rw-r--r--svl/qa/unit/test_SvAddressParser.cxx77
-rw-r--r--svl/qa/unit/test_URIHelper.cxx528
-rw-r--r--svl/qa/unit/test_lngmisc.cxx168
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: */