[Libreoffice-commits] core.git: Branch 'feature/unitver' - 1990 commits - accessibility/inc accessibility/source android/Bootstrap android/CustomTarget_android_desktop.mk android/CustomTarget_lo_android.mk android/experimental android/Makefile android/mobile-config.py avmedia/inc avmedia/source basctl/inc basctl/source basebmp/source basebmp/test basegfx/inc basegfx/source basic/inc basic/Library_sb.mk basic/qa basic/source basic/util bean/native binaryurp/source bin/extract-tooltip.py bin/get-bugzilla-attachments-by-mimetype bin/run bridges/inc bridges/Library_cpp_uno.mk bridges/source canvas/Library_cairocanvas.mk canvas/Library_oglcanvas.mk canvas/source canvas/workben chart2/CppunitTest_chart2_export.mk chart2/CppunitTest_chart2_import.mk chart2/CppunitTest_chart2_xshape.mk chart2/inc chart2/Library_chartcore.mk chart2/qa chart2/source cli_ure/readme.txt cli_ure/source codemaker/source comphelper/inc comphelper/source compilerplugins/clang config_host/config_global.h.in config_host.mk.in conf igmgr/inc configmgr/Module_configmgr.mk configmgr/qa configmgr/source configure.ac connectivity/CppunitTest_connectivity_ado.mk connectivity/CppunitTest_connectivity_commontools.mk connectivity/inc connectivity/Library_ado.mk connectivity/Module_connectivity.mk connectivity/qa connectivity/source cppcanvas/CppunitTest_cppcanvas_emfplus.mk cppcanvas/inc cppcanvas/qa cppcanvas/source cppuhelper/inc cppuhelper/source cppu/source cui/inc cui/Library_cui.mk cui/source cui/uiconfig cui/UIConfig_cui.mk dbaccess/CppunitTest_dbaccess_dialog_save.mk dbaccess/CppunitTest_dbaccess_embeddeddb_performancetest.mk dbaccess/CppunitTest_dbaccess_empty_stdlib_save.mk dbaccess/CppunitTest_dbaccess_firebird_test.mk dbaccess/CppunitTest_dbaccess_hsqldb_test.mk dbaccess/CppunitTest_dbaccess_macros_test.mk dbaccess/CppunitTest_dbaccess_nolib_save.mk dbaccess/CppunitTest_dbaccess_RowSetClones.mk dbaccess/inc dbaccess/Library_dbu.mk dbaccess/Module_dbaccess.mk dbaccess/PythonTest_dbaccess_python.mk dbaccess/ qa dbaccess/source dbaccess/util desktop/inc desktop/Library_deploymentgui.mk desktop/Library_sofficeapp.mk desktop/Package_scripts.mk desktop/source desktop/test desktop/uiconfig desktop/unx distro-configs/LibreOfficeCoverity.conf distro-configs/LibreOfficeWin32.conf distro-configs/LibreOfficeWin64.conf download.lst drawinglayer/inc drawinglayer/source dtrans/source editeng/inc editeng/source embeddedobj/Library_emboleobj.mk embeddedobj/source embeddedobj/test embedserv/Library_emser.mk embedserv/source extensions/inc extensions/Library_oleautobridge.mk extensions/source external/boost external/coinmp external/cppunit external/firebird external/graphite external/harfbuzz external/hunspell external/icu external/jpeg-turbo external/lcms2 external/libabw external/libatomic_ops external/libcdr external/libebook external/libgltf external/libmspub external/libmwaw external/libodfgen external/liborcus external/libpagemaker external/librevenge external/libvisio external/libwpd external/lps olve external/Module_external.mk external/poppler external/python3 external/redland external/udunits2 filter/Library_pdffilter.mk filter/Library_textfd.mk filter/source filter/uiconfig forms/Library_frm.mk forms/source forms/util formula/source fpicker/source fpicker/uiconfig framework/inc framework/qa framework/source g .git-hooks/commit-msg .git-hooks/post-merge helpcompiler/source helpcontent2 hwpfilter/inc hwpfilter/source i18npool/inc i18npool/source i18npool/util icon-themes/breeze icon-themes/crystal icon-themes/galaxy icon-themes/hicontrast icon-themes/human icon-themes/industrial icon-themes/oxygen icon-themes/sifr icon-themes/tango icon-themes/tango_testing idlc/inc idlc/source idl/inc idl/source include/avmedia include/basebmp include/basegfx include/basic include/canvas include/codemaker include/com include/comphelper include/connectivity include/cppcanvas include/cppu include/cppuhelper include/dbaccess include/drawinglayer include/editeng include/filter include/formula include/framework include/i18nlangtag include/i18nutil include/jvmaccess include/LibreOfficeKit include/linguistic include/o3tl include/oox include/opencl include/osl include/registry include/rtl include/sal include/salhelper include/sax include/sfx2 include/sot include/store include/svl include/svtools include/svx include/toolkit include/tools include/typelib include/ucbhelper include/uno include/unotest include/unotools include/vbahelper include/vcl include/xmloff include/xmlreader instsetoo_native/util ios/CustomTarget_TiledLibreOffice_app.mk ios/experimental ios/shared javaunohelper/com jurt/com jvmaccess/source jvmfwk/plugins jvmfwk/source l10ntools/inc l10ntools/source libreofficekit/qa libreofficekit/README libreofficekit/source lingucomponent/source linguistic/inc linguistic/source lotuswordpro/source Makefile.fetch Makefile.in mysqlc/source nlpsolver/src nlpsolver/ThirdParty odk/config odk/examples odk/Package_odk_headers.mk odk/source offapi/com offapi/UnoApi_offapi.mk of ficecfg/registry oox/inc oox/README oox/source opencl/inc opencl/source package/inc package/source postprocess/CppunitTest_services.mk postprocess/qa pyuno/inc pyuno/source qadevOOo/runner qadevOOo/tests readlicense_oo/license README.Android registry/source registry/tools registry/workben reportbuilder/java reportdesign/inc reportdesign/source RepositoryExternal.mk RepositoryFixes.mk Repository.mk RepositoryModule_host.mk rsc/inc rsc/source sal/cpprt sal/cppunittester sal/CppunitTest_sal_rtl_oustringbuffer.mk sal/inc sal/Library_sal.mk sal/osl sal/qa sal/rtl sal/textenc sal/workben sax/qa sax/source scaddins/source sc/AllLangResTarget_sc.mk sc/CppunitTest_sc_annotationobj.mk sc/CppunitTest_sc_annotationshapeobj.mk sc/CppunitTest_sc_annotationsobj.mk sc/CppunitTest_sc_bugfix_test.mk sc/CppunitTest_sc_cellrangeobj.mk sc/CppunitTest_sc_chart_regression_test.mk sc/CppunitTest_sc_condformats.mk sc/CppunitTest_sc_databaserangeobj.mk sc/CppunitTest_sc_datapilotfieldobj.mk sc/CppunitTest_sc _datapilottableobj.mk sc/CppunitTest_sc_editfieldobj_cell.mk sc/CppunitTest_sc_editfieldobj_header.mk sc/CppunitTest_sc_html_export_test.mk sc/CppunitTest_sc_macros_test.mk sc/CppunitTest_sc_modelobj.mk sc/CppunitTest_sc_namedrangeobj.mk sc/CppunitTest_sc_namedrangesobj.mk sc/CppunitTest_sc_opencl_test.mk sc/CppunitTest_sc_outlineobj.mk sc/CppunitTest_sc_perfobj.mk sc/CppunitTest_sc_rangelst_test.mk sc/CppunitTest_sc_recordchanges.mk sc/CppunitTest_sc_styleloaderobj.mk sc/CppunitTest_sc_tablesheetobj.mk sc/CppunitTest_sc_tablesheetsobj.mk sc/CppunitTest_sc_ucalc.mk sc/CppunitTest_sc_units.mk sc/inc sc/Library_sc.mk sc/Module_sc.mk scp2/AutoInstall.mk scp2/InstallModule_accessories.mk scp2/InstallModule_impress.mk scp2/InstallModule_ooo.mk scp2/InstallModule_python.mk scp2/InstallModule_xsltfilter.mk scp2/source sc/qa scripting/examples scripting/java scripting/source sc/source sc/uiconfig sc/workben sd/CppunitTest_sd_export_tests.mk sd/CppunitTest_sd_html_export_tests.mk sd/CppunitT est_sd_import_tests.mk sd/CppunitTest_sd_uimpress.mk sdext/inc sdext/source sd/inc sd/Module_sd.mk sd/qa sd/source sd/uiconfig setup_native/Library_sellangmsi.mk setup_native/Library_shlxtmsi.mk setup_native/Package_scripts.mk setup_native/source sfx2/inc sfx2/source sfx2/uiconfig shell/inc shell/Library_ooofilt.mk shell/Library_ooofilt_x64.mk shell/Library_propertyhdl.mk shell/Library_propertyhdl_x64.mk shell/Library_shlxthdl.mk shell/Library_shlxthdl_x64.mk shell/Package_scripts_kde.mk shell/source shell/StaticLibrary_shlxthandler_common.mk shell/StaticLibrary_shlxthandler_common_x64.mk slideshow/Library_OGLTrans.mk slideshow/source solenv/bin solenv/gbuild solenv/gdb solenv/inc soltools/mkdepend sot/inc sot/source starmath/inc starmath/Library_sm.mk starmath/qa starmath/source stoc/inc stoc/Library_bootstrap.mk stoc/Library_stocservices.mk stoc/source stoc/test stoc/util store/source svgio/inc svgio/source svl/inc svl/qa svl/source svl/unx svtools/inc svtools/qa svtools/source sv x/Executable_gengal.mk svx/inc svx/sdi svx/source svx/uiconfig sw/CppunitTest_sw_globalfilter.mk sw/CppunitTest_sw_htmlexport.mk sw/CppunitTest_sw_htmlimport.mk sw/CppunitTest_sw_layout_test.mk sw/CppunitTest_sw_macros_test.mk sw/CppunitTest_sw_mailmerge.mk sw/CppunitTest_sw_odfexport.mk sw/CppunitTest_sw_odfimport.mk sw/CppunitTest_sw_ooxmlfieldexport.mk sw/CppunitTest_sw_ooxmlimport.mk sw/CppunitTest_sw_ooxmlsdrexport.mk sw/CppunitTest_sw_ooxmlw14export.mk sw/CppunitTest_sw_rtfexport.mk sw/CppunitTest_sw_rtfimport.mk sw/CppunitTest_sw_uiwriter.mk sw/CppunitTest_sw_ww8export.mk sw/CppunitTest_sw_ww8import.mk sw/Executable_tiledrendering.mk sw/inc sw/Library_swd.mk sw/Library_sw.mk sw/Library_swui.mk sw/Module_sw.mk sw/ooxmlexport_setup.mk sw/qa sw/source sw/uiconfig sw/util test/source testtools/source toolkit/qa toolkit/source tools/inc tools/source translations ucb/source udkapi/com UnoControls/source unoidl/source unotest/Library_unobootstrapprotector.mk unotest/source unotools/ inc unotools/qa unotools/source unoxml/inc unoxml/source unusedcode.easy ure/source uui/inc uui/source vbahelper/inc vbahelper/source vcl/Executable_icontest.mk vcl/Executable_mtfdemo.mk vcl/Executable_ui-previewer.mk vcl/Executable_vcldemo.mk vcl/generic vcl/headless vcl/inc vcl/Library_vcl.mk vcl/Library_vclplug_gen.mk vcl/Library_vclplug_gtk3.mk vcl/Library_vclplug_svp.mk vcl/Module_vcl.mk vcl/null vcl/opengl vcl/osx vcl/qa vcl/quartz vcl/README vcl/source vcl/StaticLibrary_glxtest.mk vcl/StaticLibrary_headless.mk vcl/unx vcl/win vcl/WinResTarget_vcl.mk vcl/workben winaccessibility/source wizards/com wizards/source writerfilter/inc writerfilter/Library_writerfilter.mk writerfilter/source writerfilter/util writerperfect/Library_wpftimpress.mk writerperfect/source xmerge/source xmlhelp/source xmloff/inc xmloff/source xmlscript/dtd xmlscript/inc xmlscript/source xmlsecurity/inc xmlsecurity/source

Andrzej Hunt andrzej at ahunt.org
Sun Mar 8 09:46:38 PDT 2015


Rebased ref, commits from common ancestor:
commit b8cf2f2195f1085bb22bed1ff7df0ec469abd53e
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 8 16:41:58 2015 +0000

    Fix firebird linking.
    
    Change-Id: Iab5d4e8d0c75310d9347ac910c45e6aaa8216ae1

diff --git a/external/udunits2/ExternalPackage_udunits2.mk b/external/udunits2/ExternalPackage_udunits2.mk
index a93da13..4073463 100644
--- a/external/udunits2/ExternalPackage_udunits2.mk
+++ b/external/udunits2/ExternalPackage_udunits2.mk
@@ -16,7 +16,9 @@ $(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/iudunits2.
 else ifeq ($(OS),MACOSX)
 $(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/libudunits2.dylib,lib/.libs/libudunits2.dylib))
 else
-$(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/libudunits2.so.0,lib/.libs/libudunits2.so.0.1.0))
+$(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/libudunits2.so,lib/.libs/libudunits2.so))
+$(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/libudunits2.so.0,lib/.libs/libudunits2.so.0))
+$(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_LIB_FOLDER)/libudunits2.so.0.1.0,lib/.libs/libudunits2.so.0.1.0))
 endif
 
 $(eval $(call gb_ExternalPackage_add_file,udunits2,$(LIBO_SHARE_FOLDER)/udunits2/udunits2.xml,lib/udunits2.xml))
commit f002de42cc9ee5c8e302a230562768b43ac28bc8
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 8 12:47:57 2015 +0000

    Implement infobar warning for unit errors in a formula.
    
    Change-Id: I2a909c9b71ff33754096da03d4cc7bbe390c2e1b

diff --git a/sc/AllLangResTarget_sc.mk b/sc/AllLangResTarget_sc.mk
index d41a13f..e6637ea 100644
--- a/sc/AllLangResTarget_sc.mk
+++ b/sc/AllLangResTarget_sc.mk
@@ -43,6 +43,7 @@ $(eval $(call gb_SrsTarget_add_files,sc/res,\
     sc/source/ui/src/scstring.src \
     sc/source/ui/src/filter.src \
     sc/source/ui/src/condformatdlg.src \
+    sc/source/ui/src/units.src \
     sc/source/ui/cctrl/checklistmenu.src \
     sc/source/ui/navipi/navipi.src \
     sc/source/ui/styleui/scstyles.src \
diff --git a/sc/inc/sc.hrc b/sc/inc/sc.hrc
index f76a45a..b484ae6 100644
--- a/sc/inc/sc.hrc
+++ b/sc/inc/sc.hrc
@@ -990,7 +990,14 @@
 #define STR_TITLE_AUTHOR        (STR_START + 442)
 #define STR_TITLE_DATE          (STR_START + 443)
 #define STR_UNKNOWN_USER_CONFLICT    (STR_START + 444)
-#define STR_END                 (STR_UNKNOWN_USER_CONFLICT)
+
+#define STR_UNITS_ERRORINCELL   (STR_START + 450)
+#define BT_UNITS_EDIT_CELL      (STR_START + 451)
+
+#define STR_UNITS_CONV_REQUIRED (STR_START + 455)
+#define BT_UNITS_CONV_THISCELL  (STR_START + 456)
+#define BT_UNITS_CONV_ALL       (STR_START + 457)
+#define STR_END                 (BT_UNITS_CONV_ALL)
 
 #define BMP_START               (STR_END)
 
diff --git a/sc/source/ui/inc/viewfunc.hxx b/sc/source/ui/inc/viewfunc.hxx
index 70c7e0f..ec7598a 100644
--- a/sc/source/ui/inc/viewfunc.hxx
+++ b/sc/source/ui/inc/viewfunc.hxx
@@ -368,6 +368,9 @@ private:
                                         bool bAttrChanged, bool bAddUndo );
 
     void            MarkAndJumpToRanges(const ScRangeList& rRanges);
+
+    void            NotifyUnitErrorInFormula( const ScAddress& rAddress, ScDocument* pDoc );
+    DECL_LINK( EditUnitErrorFormulaHandler, PushButton* );
 };
 
 #endif
diff --git a/sc/source/ui/src/units.src b/sc/source/ui/src/units.src
new file mode 100644
index 0000000..6165af8
--- /dev/null
+++ b/sc/source/ui/src/units.src
@@ -0,0 +1,53 @@
+/* -*- 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 "sc.hrc"
+
+String STR_UNITS_ERRORINCELL
+{
+    Text [ en-US ] = "Error in formula in Cell $1" ;
+};
+
+PushButton BT_UNITS_EDIT_CELL
+{
+    Pos = MAP_APPFONT( 0 , 0 );
+    Size = MAP_APPFONT( 70 , 0 );
+    Text[ en-US ] = "Edit Formula";
+};
+
+String STR_UNITS_CONV_REQUIRED
+{
+    Text [ en-US ] = "You entered data in $1, would you like to convert to $2?" ;
+};
+
+PushButton BT_UNITS_CONV_THISCELL
+{
+    Pos = MAP_APPFONT( 0 , 0 );
+    Size = MAP_APPFONT( 70 , 0 );
+    Text [ en-US ] = "Convert (only this cell)" ;
+};
+
+PushButton BT_UNITS_CONV_ALL
+{
+    Pos = MAP_APPFONT( 0 , 0 );
+    Size = MAP_APPFONT( 70 , 0 );
+    Text [ en-US ] = "Convert (automatically for all cells needing $2)" ;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx
index 1edd7a6..8d9dac7 100644
--- a/sc/source/ui/view/viewfunc.cxx
+++ b/sc/source/ui/view/viewfunc.cxx
@@ -31,6 +31,7 @@
 #include <editeng/scripttypeitem.hxx>
 #include <editeng/justifyitem.hxx>
 #include <sfx2/bindings.hxx>
+#include <sfx2/infobar.hxx>
 #include <svl/zforlist.hxx>
 #include <svl/zformat.hxx>
 #include <vcl/msgbox.hxx>
@@ -473,6 +474,7 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab,
             else
             {
                 SAL_INFO( "sc.units", "verification failed" );
+                NotifyUnitErrorInFormula( aPos, pDoc );
             }
 #endif
         } while ( bAgain );
@@ -2810,4 +2812,30 @@ void ScViewFunc::UpdateSelectionArea( const ScMarkData& rSel, ScPatternAttr* pAt
     pTabViewShell->AdjustBlockHeight(false, const_cast<ScMarkData*>(&rSel));
 }
 
+void ScViewFunc::NotifyUnitErrorInFormula( const ScAddress& rAddress, ScDocument* pDoc )
+{
+    SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
+
+    OUString sTitle = SC_RESSTR( STR_UNITS_ERRORINCELL );
+    sTitle = sTitle.replaceAll( "$1", rAddress.GetColRowString() );
+    OUString sCellAddress = rAddress.Format( SCA_BITS, pDoc );
+    SfxInfoBarWindow* pInfoBar = pViewFrame->AppendInfoBar( sCellAddress, sTitle );
+
+    assert( pInfoBar );
+    PushButton* pButtonGotoCell = new PushButton( &pViewFrame->GetWindow(), ScResId(BT_UNITS_EDIT_CELL) );
+    pButtonGotoCell->SetClickHdl( LINK( this, ScViewFunc, EditUnitErrorFormulaHandler ) );
+    pInfoBar->addButton( pButtonGotoCell);
+}
+
+IMPL_LINK( ScViewFunc, EditUnitErrorFormulaHandler, PushButton*, pButton )
+{
+    SfxInfoBarWindow* pInfoBar = dynamic_cast< SfxInfoBarWindow* >( pButton->GetParent() );
+    const OUString sCell = pInfoBar->getId();
+    ScAddress aAddress;
+    aAddress.Parse( sCell );
+
+    (void) aAddress; // TODO: implement
+    return 0;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 229d884c20fb2b4e4e614ab29ed6771d7e23b9a6
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 8 12:42:47 2015 +0000

    Make SfxInfoBarWindow SFX2_DLLPUBLIC.
    
    We need this to be able to add buttons to the infobar from calc
    (for use in the unit verification UI).
    
    Change-Id: Iaffab714f926b8d70d047215e62fa7fd48eff832

diff --git a/include/sfx2/infobar.hxx b/include/sfx2/infobar.hxx
index eb0c4c6..0a15d33 100644
--- a/include/sfx2/infobar.hxx
+++ b/include/sfx2/infobar.hxx
@@ -40,7 +40,7 @@ class SFX2_DLLPUBLIC SfxInfoBarContainerChild : public SfxChildWindow
 
 /** Class representing a single InfoBar to be added in a SfxInfoBarContainerWindow.
   */
-class SfxInfoBarWindow : public vcl::Window
+class SFX2_DLLPUBLIC SfxInfoBarWindow : public vcl::Window
 {
     private:
         OUString m_sId;
commit 829baed96abe8b116ae5a8709c24e04b2d35c286
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Fri Mar 6 09:16:53 2015 +0000

    Extract units from headers for unit verification too.
    
    The default for unit annotations would be to have them in column
    headers, the per-cell annotation is less useful in practice.
    
    Change-Id: I593e8c5846018686ac5a3fa1cf865a3676cb3900

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 898582a..4a055d4 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -301,15 +301,44 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
     // Addresses can/will be relative to the formula, for extracting
     // units however we will need to get the absolute address (i.e.
     // by adding the current address to the relative formula address).
-    ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
+    const ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
 
     OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc);
 
     UtUnit aUnit;
-    if (!UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
-        SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
+    if (sUnitString.getLength() > 0 &&
+        UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
+        return aUnit;
     }
 
+    // Scan UPwards from the current cell to find a header. This is since we could potentially
+    // have two different sets of data sharing a column, hence finding the closest header is necessary.
+    ScAddress aAddress = aInputAddress;
+    while (aAddress.Row() > 1) {
+        aAddress.IncRow(-1);
+
+        // We specifically test for string cells as intervening data cells could have
+        // differently defined units of their own. (However as these intervening cells
+        // will have the unit stored in the number format it would be ignored when
+        // checking the cell's string anyway.)
+        if (pDoc->GetCellType(aAddress) == CELLTYPE_STRING &&
+            extractUnitFromHeaderString(pDoc->GetString(aAddress), aUnit)) {
+            // TODO: one potential problem is that we could have a text only "united" data cell
+            // (where the unit wasn't automatically extracted due to being entered via
+            // a different spreadsheet program).
+            // We could solve that maybe by trying the unit extraction for such cells first?
+            // (I.e if(extractUnitStringForCell(...)) -> do the splitUnitsFrom... dance.
+            //
+            // TODO: and what if there are multiple units in the header (for whatever reason?)?
+            // We can probably just warn the user that we'll be giving them garbage in that case?
+            return aUnit;
+        }
+    }
+
+    SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
+
+    // We return the dimensionless unit 1 if we don't find any other data suggesting a unit.
+    UtUnit::createUnit("", aUnit, mpUnitSystem);
     return aUnit;
 }
 
commit f51fe1d5b856d899b14a65fbcc1bc98b679fbcf4
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Fri Mar 6 09:09:27 2015 +0000

    Implement header extraction for non-bracketed units.
    
    Change-Id: I6f0ec0b2d4cb73531f5adcfa5fd6d0630820e32e

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 2d6dca9..97acef6 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -262,6 +262,7 @@ void UnitsTest::testUnitFromHeaderExtraction() {
 
     OUString sEmpty = "";
     CPPUNIT_ASSERT(!mpUnitsImpl->extractUnitFromHeaderString(sEmpty, aUnit));
+    CPPUNIT_ASSERT(aUnit == UtUnit());
 
     OUString sSimple = "bla bla [cm/s]";
     CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sSimple, aUnit));
@@ -271,6 +272,32 @@ void UnitsTest::testUnitFromHeaderExtraction() {
     UtUnit aTestUnit;
     CPPUNIT_ASSERT(UtUnit::createUnit("cm/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+
+    OUString sFreeStanding = "bla bla kg/h";
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit));
+    CPPUNIT_ASSERT(UtUnit::createUnit("kg/h", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    CPPUNIT_ASSERT(aUnit == aTestUnit);
+
+    OUString sFreeStandingWithSpaces = "bla bla m / s";
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStandingWithSpaces, aUnit));
+    CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    CPPUNIT_ASSERT(aUnit == aTestUnit);
+
+    OUString sOperatorSeparated = "bla bla / t/s";
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sOperatorSeparated, aUnit));
+    CPPUNIT_ASSERT(UtUnit::createUnit("t/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    CPPUNIT_ASSERT(aUnit == aTestUnit);
+
+    OUString sRoundBrackets = "bla bla (t/h)";
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sRoundBrackets, aUnit));
+    CPPUNIT_ASSERT(UtUnit::createUnit("t/h", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    CPPUNIT_ASSERT(aUnit == aTestUnit);
+
+    // This becomes more of a nightmare to support, so let's not bother for now.
+    // OUString sFreeStandingMixedSpaces = "bla bla m /s* kg";
+    // CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit));
+    // CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    // CPPUNIT_ASSERT(aUnit == aTestUnit);
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 49fb48b..898582a 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -254,7 +254,37 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
     }
 
     // 2. We try to check for any free-standing units by splitting the string and testing each split
-    // TODO: do this.
+    const sal_Int32 nTokenCount = comphelper::string::getTokenCount(rString, ' ');
+    // There's an inherent limit to how well we can cope with various spacing issues here without
+    // a ton of computational complexity.
+    // E.g. by parsing in this way we might end up with unmatched parentheses which udunits won't like.
+    // for now operators have to be completely freestanding i.e. "cm / kg" or completely within a valid unit string e.g. "cm/kg"
+    const OUString sOperators = "/*"; // valid
+    OUString sCurrent = "";
+    for (sal_Int32 nToken = 0; nToken < nTokenCount; nToken++) {
+        OUString sToken = rString.getToken(nToken, ' ');
+        UtUnit aTestUnit;
+        if (UtUnit::createUnit(sToken, aTestUnit, mpUnitSystem) ||
+            // Only test for a separator character if we have already got something in our string, as
+            // some of the operators could be used as separators from description to unit
+            // (e.g. "a description / kg").
+            ((sCurrent.getLength() > 0) && (sToken.getLength() == 1) && (sOperators.indexOf(sToken[0]) != -1))) {
+                // we're repeatedly testing the string hence using an OUStringBuffer isn't of much use since there's
+                // no simple/efficient way of repeatedly getting a testable OUString from the buffer.
+                sCurrent += sToken;
+        } else if (sCurrent.getLength() > 0) {
+            // If we have units, followed by text, followed by units, we should still flag an error since
+            // that's ambiguous (unless the desired units are enclose in [] in which case we've
+            // already extracted these desired units in step 1 above.
+            break;
+        }
+    }
+
+    // We test the length to make sure we don't return the dimensionless unit 1 if we haven't found any units
+    // in the header.
+    if (sCurrent.getLength() && UtUnit::createUnit(sCurrent, aUnit, mpUnitSystem)) {
+        return true;
+    }
 
     // 3. Give up
     aUnit = UtUnit(); // assign invalid
commit f277fe07baa8835ea082e6a62ec0d0c6214e9cb6
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Fri Mar 6 08:20:24 2015 +0000

    Implement basic header string unit extraction.
    
    Some more complex types of headers (e.g. units not within brackets)
    aren't yet implemented.
    
    Change-Id: I3e4d1ca5fd80ad1bbd84218ed38141fbcfca13b6

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index f6a0aa0..2d6dca9 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -45,6 +45,7 @@ public:
     void testUnitFromFormatStringExtraction();
     void testUnitValueStringSplitting();
 
+    void testUnitFromHeaderExtraction();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
 
@@ -53,6 +54,7 @@ public:
 
     CPPUNIT_TEST(testUnitFromFormatStringExtraction);
     CPPUNIT_TEST(testUnitValueStringSplitting);
+    CPPUNIT_TEST(testUnitFromHeaderExtraction);
 
     CPPUNIT_TEST_SUITE_END();
 
@@ -255,6 +257,22 @@ void UnitsTest::testUnitValueStringSplitting() {
     CPPUNIT_ASSERT(sUnit == "kg");
 }
 
+void UnitsTest::testUnitFromHeaderExtraction() {
+    UtUnit aUnit;
+
+    OUString sEmpty = "";
+    CPPUNIT_ASSERT(!mpUnitsImpl->extractUnitFromHeaderString(sEmpty, aUnit));
+
+    OUString sSimple = "bla bla [cm/s]";
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sSimple, aUnit));
+    // We need to test in Units (rather than testing Unit::getString()) as
+    // any given unit can have multiple string representations (and utunits defaults to
+    // representing e.g. cm as (I think) "0.01m").
+    UtUnit aTestUnit;
+    CPPUNIT_ASSERT(UtUnit::createUnit("cm/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
+    CPPUNIT_ASSERT(aUnit == aTestUnit);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 5b972d6..49fb48b 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -13,15 +13,23 @@
 
 #include "document.hxx"
 #include "refdata.hxx"
+#include "stringutil.hxx"
 #include "tokenarray.hxx"
 
+#include <comphelper/string.hxx>
 #include <osl/file.hxx>
 #include <osl/mutex.hxx>
 #include <rtl/bootstrap.hxx>
 #include <svl/zformat.hxx>
 
+#include <com/sun/star/util/SearchFlags.hpp>
+#include <com/sun/star/util/SearchAlgorithms.hpp>
+#include <com/sun/star/util/SearchOptions.hpp>
+#include <com/sun/star/util/XTextSearch.hpp>
+
 #include <boost/scoped_array.hpp>
 
+using namespace com::sun::star;
 using namespace formula;
 using namespace sc;
 using namespace sc::units;
@@ -200,6 +208,59 @@ OUString UnitsImpl::extractUnitStringForCell(const ScAddress& rAddress, ScDocume
     return extractUnitStringFromFormat(rFormatString);
 }
 
+bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit) {
+    com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
+
+    uno::Reference<lang::XMultiServiceFactory> xFactory(xContext->getServiceManager(), uno::UNO_QUERY_THROW);
+
+    uno::Reference<util::XTextSearch> xSearch =
+        uno::Reference< util::XTextSearch >(
+            xFactory->createInstance(
+                "com.sun.star.util.TextSearch"), uno::UNO_QUERY_THROW);
+
+    util::SearchOptions aOptions;
+    aOptions.algorithmType = util::SearchAlgorithms_REGEXP ;
+    aOptions.searchFlag = util::SearchFlags::ALL_IGNORE_CASE;
+
+    // 1. We try to check for units contained within square brackets
+    // TODO: we should do a sanity check that there's only one such unit though (and fail if there are multiple).
+    //       Since otherwise there's no way for us to know which unit is the intended one, hence we need to get
+    //       the user to deconfuse us by correcting their header to only contain the intended unit.
+    aOptions.searchString = "\\[([^\\]]+)\\]"; // Grab the contents between [ and ].
+    xSearch->setOptions( aOptions );
+
+    util::SearchResult aResult;
+    sal_Int32 nStartPosition = rString.getLength();
+    while (nStartPosition) {
+        // Search from the back since units are more likely to be at the end of the header.
+        aResult = xSearch->searchBackward(rString, nStartPosition, 0);
+
+        // We have either 0 items (no match), or 2 (matched string + the group within)
+        if (aResult.subRegExpressions != 2) {
+            break;
+        } else {
+            // Confusingly (to me) when doing a backwards search we end up with: endOffset < startOffset.
+            // i.e. startOffset is the last character of the intended substring, endOffset the first character.
+            // We specifically grab the offsets for the first actual regex group, which are stored in [1], the indexes
+            // at [0] represent the whole matched string (i.e. including square brackets).
+            OUString sUnitString = rString.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]);
+
+            if (UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
+                return true;
+            }
+
+            nStartPosition = aResult.endOffset[0];
+        }
+    }
+
+    // 2. We try to check for any free-standing units by splitting the string and testing each split
+    // TODO: do this.
+
+    // 3. Give up
+    aUnit = UtUnit(); // assign invalid
+    return false;
+}
+
 UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
                     ScDocument* pDoc) {
     assert(pToken->GetType() == formula::svSingleRef);
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index f37ad5c..909916a 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -67,6 +67,7 @@ private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
     OUString extractUnitStringFromFormat(const OUString& rFormatString);
     OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
+    bool extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);
commit 5d0b71f4324949db0dbac1de0b5615f1ada290c9
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Mar 5 19:30:32 2015 +0000

    Add some const.
    
    Change-Id: I8cabaa6b5fd2df6bbb7db4e9c0a209b6ab305b38

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 0fc7f85..5b972d6 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -191,7 +191,7 @@ OUString UnitsImpl::extractUnitStringFromFormat(const OUString& rFormatString) {
 }
 
 
-OUString UnitsImpl::extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc) {
+OUString UnitsImpl::extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc) {
     sal_uInt32 nFormat = pDoc->GetNumberFormat(rAddress);
     const SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
     const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 1009a95..f37ad5c 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -66,7 +66,7 @@ public:
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
     OUString extractUnitStringFromFormat(const OUString& rFormatString);
-    OUString extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc);
+    OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);
commit 3e8a1d6f99e99572010eaa51f4c8b3fd0f191eec
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sat Feb 7 11:33:39 2015 +0000

    Implement actual unit extraction / format setting.
    
    This makes the unit verification automatic (modulo no UI),
    but is still very hacky.
    
    Change-Id: Iff6f97b2c070e1caf2911533339cb3f07b259ed4

diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
index 4c0bf2a..42e9db8 100644
--- a/sc/source/core/data/column3.cxx
+++ b/sc/source/core/data/column3.cxx
@@ -1672,6 +1672,33 @@ bool ScColumn::ParseString(
         {
             if (aParam.mbDetectNumberFormat)
             {
+#ifdef ENABLE_CALC_UNITVERIFICATION
+                OUString sValue, sUnit;
+                boost::shared_ptr< sc::units::Units > pUnits = sc::units::Units::GetUnits();
+                if (pUnits->splitUnitsFromInputString(rString, sValue, sUnit)) {
+                    // TODO we should check whether a suitable format already exists.
+                    // We also want to ideally preserve whatever format would be used
+                    // for the actual numbers (e.g. 1E4 is preserved as 1E4 unless we
+                    // now set the number format as #"foo" in which case the raw number is
+                    // displayed i.e. 1000foo)
+
+                    // I.e. it may be more sensible to extract the unit here, continue processing as normal
+                    // and then at the end add the unit to the format. In fact we should probably ALWAYS
+                    // remove the unit whenever doing any format processing (i.e. everywhere), and reappend it after?
+                    // But in that case it would make sense to actually store units independently of number format.
+
+                    // (This is all just a dirty hack for now...)
+                    OUString sNewFormat = "#\"" + sUnit + "\"";
+                    sal_uInt32 nFormatKey;
+                    short nType = css::util::NumberFormat::DEFINED;
+                    sal_Int32 nErrorPosition; // Unused, because we should be creating working number formats.
+
+                    aParam.mpNumFormatter->PutEntry(sNewFormat, nErrorPosition, nType, nFormatKey);
+                    SetNumberFormat(nRow, nFormatKey);
+
+                    nIndex = nFormatKey;
+                }
+#endif
                 if (!aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal))
                     break;
 
commit 804a553367e565c3dc4a6c613e5ec813098fdfc9
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Fri Feb 6 18:46:30 2015 +0000

    Implement splitUnitsFromInputString.
    
    Change-Id: I1f781948e57c37dc3adbfcd14cb3d6ba488c10a0

diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx
index cfeae9e..9561b40 100644
--- a/sc/inc/units.hxx
+++ b/sc/inc/units.hxx
@@ -10,6 +10,8 @@
 #ifndef INCLUDED_SC_INC_UNITS_HXX
 #define INCLUDED_SC_INC_UNITS_HXX
 
+#include <rtl/ustring.hxx>
+
 #include <boost/shared_ptr.hpp>
 
 class ScAddress;
@@ -27,6 +29,16 @@ public:
 
     virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) = 0;
 
+    /*
+     * Split the input into value and unit, where rInput == rValue + rUnit.
+     * (We assume that the unit is always the last part of the input string.)
+     *
+     * Returns whether or not the string has been split.
+     * rValue and rUnit are always set to valid values, irrespective of string
+     * splitting having actually taken place.
+     */
+    virtual bool splitUnitsFromInputString(const OUString& rInput, OUString& rValue, OUString& rUnit) = 0;
+
     virtual ~Units() {}
 };
 
diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 3515254..f6a0aa0 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -38,15 +38,22 @@ public:
 
     ::boost::shared_ptr< UnitsImpl > mpUnitsImpl;
 
+
     void testUTUnit();
     void testUnitVerification();
 
     void testUnitFromFormatStringExtraction();
+    void testUnitValueStringSplitting();
+
 
     CPPUNIT_TEST_SUITE(UnitsTest);
+
     CPPUNIT_TEST(testUTUnit);
     CPPUNIT_TEST(testUnitVerification);
+
     CPPUNIT_TEST(testUnitFromFormatStringExtraction);
+    CPPUNIT_TEST(testUnitValueStringSplitting);
+
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -209,6 +216,45 @@ void UnitsTest::testUnitFromFormatStringExtraction() {
     CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
 }
 
+void UnitsTest::testUnitValueStringSplitting() {
+    OUString sValue, sUnit;
+
+    OUString sEmptyString = "";
+    CPPUNIT_ASSERT(!mpUnitsImpl->splitUnitsFromInputString(sEmptyString, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue.isEmpty());
+    CPPUNIT_ASSERT(sUnit.isEmpty());
+
+    OUString sNumberOnlyString = "10";
+    CPPUNIT_ASSERT(!mpUnitsImpl->splitUnitsFromInputString(sNumberOnlyString, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "10");
+    CPPUNIT_ASSERT(sUnit.isEmpty());
+
+    OUString sTextOnlyString = "hello world";
+    CPPUNIT_ASSERT(!mpUnitsImpl->splitUnitsFromInputString(sTextOnlyString, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "hello world");
+    CPPUNIT_ASSERT(sUnit.isEmpty());
+
+    OUString sDeceptiveInput = "30garbage";
+    CPPUNIT_ASSERT(!mpUnitsImpl->splitUnitsFromInputString(sDeceptiveInput, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "30garbage");
+    CPPUNIT_ASSERT(sUnit.isEmpty());
+
+    OUString sUnitOnly = "cm";
+    CPPUNIT_ASSERT(!mpUnitsImpl->splitUnitsFromInputString(sUnitOnly, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "cm");
+    CPPUNIT_ASSERT(sUnit.isEmpty());
+
+    OUString sSimpleUnitedValue = "20m/s";
+    CPPUNIT_ASSERT(mpUnitsImpl->splitUnitsFromInputString(sSimpleUnitedValue, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "20");
+    CPPUNIT_ASSERT(sUnit == "m/s");
+
+    OUString sMultipleTokens = "40E-4kg";
+    CPPUNIT_ASSERT(mpUnitsImpl->splitUnitsFromInputString(sMultipleTokens, sValue, sUnit));
+    CPPUNIT_ASSERT(sValue == "40E-4");
+    CPPUNIT_ASSERT(sUnit == "kg");
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index d51350e5..0fc7f85 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -282,5 +282,40 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
     return true;
 }
 
+bool IsDigit(sal_Unicode c) {
+    return (c>= '0' && c <= '9');
+}
+
+bool UnitsImpl::splitUnitsFromInputString(const OUString& rInput, OUString& rValueOut, OUString& rUnitOut) {
+    int nPos = rInput.getLength();
+
+    while (nPos) {
+        if (IsDigit(rInput[nPos-1])) {
+            break;
+        }
+        nPos--;
+    }
+
+    rUnitOut = rInput.copy(nPos);
+
+    UtUnit aUnit;
+    // If the entire input is a string (nPos == 0) then treating it as a unit
+    // makes little sense as there is no numerical value associated with it.
+    // Hence it makes sense to skip testing in this case.
+    // We also need to specifically ignore the no unit case (nPos == rInput.getLength())
+    // as otherwise we are obtaining the unit for "" which is a valid unit
+    // (the dimensionless) unit, even though in reality we should obtain no unit
+    // and return false.
+    if ((nPos < rInput.getLength())
+        && (nPos > 0)
+        && UtUnit::createUnit(rUnitOut, aUnit, mpUnitSystem)) {
+        rValueOut = rInput.copy(0, nPos);
+        return true;
+    } else {
+        rValueOut = rInput;
+        rUnitOut.clear();
+        return false;
+    }
+}
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 2561e1e..1009a95 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -61,6 +61,7 @@ public:
     virtual ~UnitsImpl();
 
     virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) SAL_OVERRIDE;
+    virtual bool splitUnitsFromInputString(const OUString& rInput, OUString& rValue, OUString& rUnit) SAL_OVERRIDE;
 
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
commit 02cc21e8ebccd5c31e79b8476b114c096b90b640
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 20:21:37 2015 +0000

    Move and rename string extraction test.
    
    Change-Id: I39574e227b4d2f3ff0df8c36b1c96337d5fcbfb7

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 062262e..3515254 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -39,13 +39,14 @@ public:
     ::boost::shared_ptr< UnitsImpl > mpUnitsImpl;
 
     void testUTUnit();
-    void testStringExtraction();
     void testUnitVerification();
 
+    void testUnitFromFormatStringExtraction();
+
     CPPUNIT_TEST_SUITE(UnitsTest);
     CPPUNIT_TEST(testUTUnit);
-    CPPUNIT_TEST(testStringExtraction);
     CPPUNIT_TEST(testUnitVerification);
+    CPPUNIT_TEST(testUnitFromFormatStringExtraction);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -98,11 +99,6 @@ void UnitsTest::testUTUnit() {
     CPPUNIT_ASSERT(aM/aS == aM_S);
 }
 
-void UnitsTest::testStringExtraction() {
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
-}
-
 void UnitsTest::testUnitVerification() {
     // Make sure we have at least one tab to work with
     mpDoc->EnsureTable(0);
@@ -208,6 +204,11 @@ void UnitsTest::testUnitVerification() {
     CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
 }
 
+void UnitsTest::testUnitFromFormatStringExtraction() {
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
commit 92ee1b3f931d420594aa270941f387df3513ec22
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 18:17:09 2015 +0000

    Add stream-printing operator<< for UtUnit.
    
    Change-Id: I1c36415d673841683fe9007f17bee110a494baa7

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 344c0d1..d51350e5 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -139,7 +139,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             if (pFirstUnit == pSecondUnit) {
                 // The two units are identical, hence we can return either.
                 pOut = pFirstUnit;
-                SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
+                SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit);
             } else {
                 // TODO: notify/link UI.
             }
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index b79c057..97b6305 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -93,6 +93,13 @@ public:
     }
 };
 
+template< typename charT, typename traits >
+inline std::basic_ostream<charT, traits> & operator <<(
+    std::basic_ostream<charT, traits> & stream, const UtUnit& rUnit )
+{
+    return stream << "[" << rUnit.getString() << "]";
+}
+
 }} // namespace sc::units
 
 #endif // INCLUDED_SC_SOURCE_CORE_UNITS_UTUNIT_HXX
commit dddf872997e910893c5e3338264cd10cc3fc02ec
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 17:56:13 2015 +0000

    Add some tests for our UtUnit arithmetic operators.
    
    Change-Id: I5a29bdf306ec1980b44f54b161d0ba7df0f1a83e

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index c5013c1..062262e 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -85,6 +85,17 @@ void UnitsTest::testUTUnit() {
     // Test that we can't create garbage units
     UtUnit aGarbage;
     CPPUNIT_ASSERT(!UtUnit::createUnit("garbage", aGarbage, mpUnitsImpl->mpUnitSystem));
+
+    // Do some addition, subtraction, comparison tests.
+    UtUnit aM;
+    UtUnit::createUnit("m", aM, mpUnitsImpl->mpUnitSystem);
+    UtUnit aS;
+    UtUnit::createUnit("s", aS, mpUnitsImpl->mpUnitSystem);
+    UtUnit aM_S;
+    UtUnit::createUnit("m/s", aM_S, mpUnitsImpl->mpUnitSystem);
+
+    CPPUNIT_ASSERT(aM_S*aS == aM);
+    CPPUNIT_ASSERT(aM/aS == aM_S);
 }
 
 void UnitsTest::testStringExtraction() {
commit b4b4f1767226e5a79254d75d4c94e440993f7734
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 17:51:07 2015 +0000

    Hide raw ut_unit access in UTUnit.
    
    UtUnit exists to hide all raw fiddling with ut_unit pointers
    and the various ut_* methods.
    
    We also get rid of the implicit bool conversion and instead have
    and explicit method to check whether or not we hold a valid unit
    to make things more obvious.
    
    Change-Id: I654c47ba6daf8dfc4c2cc648150b6a86b90195bc

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index adb5d65..344c0d1 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -89,7 +89,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
 
         if (!(rUnitStack.size() >= 1)) {
             SAL_WARN("sc.units", "no units on stack for unary operation");
-            return 0;
+            return UtUnit();
         }
 
         UtUnit pUnit = rUnitStack.top();
@@ -97,8 +97,8 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
 
         switch (rOpCode) {
         case ocNot:
-            if (!ut_is_dimensionless(pUnit.get())) {
-                return 0;
+            if (!pUnit.isDimensionless()) {
+                return UtUnit();
             }
             // We just keep the same unit (in this case no unit) so can
             // fall through.
@@ -123,7 +123,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
             // TODO: what should we be telling the user in this case? Can this even happen (i.e.
             // should we just be asserting here?)
-            return 0;
+            return UtUnit();
         }
 
         UtUnit pSecondUnit = rUnitStack.top();
@@ -212,31 +212,14 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
     // by adding the current address to the relative formula address).
     ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
 
-    // udunits requires strings to be trimmed before parsing -- it's easiest to do this
-    // using the OUString utils (as opposed to using ut_trim once we have a c string.
-    OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc).trim();
+    OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc);
 
-    // empty string == dimensionless unit. ut_parse returns an error for an empty string
-    // hence we need to manually detect that case and return the dimensionless unit.
-    if (sUnitString.getLength() == 0) {
-        SAL_INFO("sc.units", "empty unit string: returning dimensionless unit");
-        return UtUnit(ut_get_dimensionless_unit_one(mpUnitSystem.get()));
-    }
-
-    SAL_INFO("sc.units", "got unit string [" << sUnitString << "]");
-    OString sUnitStringUTF8 = OUStringToOString(sUnitString, RTL_TEXTENCODING_UTF8);
-
-    // TODO: we should probably have a cache of unit strings here to save reparsing
-    // on every run?
-
-    UtUnit pUnit(ut_parse(mpUnitSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
-
-    if (!pUnit) {
+    UtUnit aUnit;
+    if (!UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
         SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
-        SAL_INFO("sc.units", "error encountered: " << getUTStatus());
     }
 
-    return pUnit;
+    return aUnit;
 }
 
 // getUnitForRef: check format -> if not in format, use more complicated method? (Format overrides header definition)
@@ -255,7 +238,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
         {
             UtUnit pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc));
 
-            if (!pUnit) {
+            if (!pUnit.isValid()) {
                 SAL_INFO("sc.units", "no unit returned for scSingleRef, ut_status: " << getUTStatus());
 
                 // This only happens in case of parsing (or system) errors.
@@ -275,7 +258,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
 
             // A null unit indicates either invalid units and/or other erronous input
             // i.e. is an indication that getOutputUnitsForOpCode failed.
-            if (pOut) {
+            if (pOut.isValid()) {
                 aUnitStack.push(pOut);
             } else {
                 return false;
diff --git a/sc/source/core/units/utunit.cxx b/sc/source/core/units/utunit.cxx
index 1cd64b7..f539502 100644
--- a/sc/source/core/units/utunit.cxx
+++ b/sc/source/core/units/utunit.cxx
@@ -20,7 +20,7 @@ bool UtUnit::createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boo
 
     UtUnit pParsedUnit(ut_parse(pUTSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
 
-    if (pParsedUnit) {
+    if (pParsedUnit.isValid()) {
         rUnitOut = pParsedUnit;
         return true;
     } else {
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index c1df459..b79c057 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -42,29 +42,40 @@ private:
         ut_free(pUnit);
     }
 
+    UtUnit(ut_unit* pUnit):
+        mpUnit(pUnit, &freeUt)
+    {}
+
+    void reset(ut_unit* pUnit) {
+        mpUnit.reset(pUnit, &freeUt);
+    }
+
+    ut_unit* get() const {
+        return mpUnit.get();
+    }
+
 public:
     static bool createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boost::shared_ptr< ut_system >& pUTSystem);
 
-    UtUnit(ut_unit* pUnit = 0):
-        mpUnit(pUnit, &freeUt)
-    {}
+    /*
+     * Default constructor returns an empty/invalid unit.
+     * (Note: this is different from the dimensionless unit which is valid.)
+     */
+    UtUnit() {};
 
     UtUnit(const UtUnit& rUnit):
         mpUnit(rUnit.mpUnit)
     {}
 
-    void reset(ut_unit* pUnit) {
-        mpUnit.reset(pUnit, &freeUt);
-    }
-
     OUString getString() const;
 
-    ut_unit* get() const {
-        return mpUnit.get();
+    bool isValid() {
+        // We use a null pointer/empty unit to indicate an invalid unit.
+        return mpUnit.get() != 0;
     }
 
-    explicit operator bool() const {
-        return mpUnit.operator bool();
+    bool isDimensionless() const {
+        return ut_is_dimensionless(this->get());
     }
 
     bool operator==(const UtUnit& rUnit) {
commit 2d90a72090e8d08c9a6eecb86a05fc069e62f105
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 17:45:01 2015 +0000

    Trim unit string for ut_parse.
    
    Change-Id: I83c32e5336b589174db92123c4959db4ac8624c8

diff --git a/sc/source/core/units/utunit.cxx b/sc/source/core/units/utunit.cxx
index 7061658..1cd64b7 100644
--- a/sc/source/core/units/utunit.cxx
+++ b/sc/source/core/units/utunit.cxx
@@ -14,7 +14,9 @@
 using namespace sc::units;
 
 bool UtUnit::createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boost::shared_ptr< ut_system >& pUTSystem) {
-    OString sUnitStringUTF8 = OUStringToOString(rUnitString, RTL_TEXTENCODING_UTF8);
+    // ut_parse requires the string to be trimmed of whitespace, it's
+    // simplest just to do this during conversion:
+    OString sUnitStringUTF8 = OUStringToOString(rUnitString.trim(), RTL_TEXTENCODING_UTF8);
 
     UtUnit pParsedUnit(ut_parse(pUTSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
 
commit 9a0b066620dc11c52406ad6e3530e303a58e7bd3
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 17:24:27 2015 +0000

    Add some basic tests for UtUnit creation.
    
    Change-Id: Ic4d2e26b383fa07b53757dd755508d42dcf88593

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index e8ec0ab..c5013c1 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -8,6 +8,7 @@
  */
 
 #include "unitsimpl.hxx"
+#include "utunit.hxx"
 
 #include "formulacell.hxx"
 
@@ -37,10 +38,12 @@ public:
 
     ::boost::shared_ptr< UnitsImpl > mpUnitsImpl;
 
+    void testUTUnit();
     void testStringExtraction();
     void testUnitVerification();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
+    CPPUNIT_TEST(testUTUnit);
     CPPUNIT_TEST(testStringExtraction);
     CPPUNIT_TEST(testUnitVerification);
     CPPUNIT_TEST_SUITE_END();
@@ -69,6 +72,21 @@ void UnitsTest::tearDown() {
     BootstrapFixture::tearDown();
 }
 
+void UnitsTest::testUTUnit() {
+    // Test that we can create units.
+    UtUnit aDimensionless;
+    CPPUNIT_ASSERT(UtUnit::createUnit("", aDimensionless, mpUnitsImpl->mpUnitSystem));
+    // And test that an empty string does in fact map to the dimensionless unit one.
+    // The documentation states that ut_is_dimensionless returns zero for dimensionless
+    // units, however the sources (and udunits2's unit tests) suggest that zero is returned
+    // for a unit WITH dimensions (as the method name would suggest).
+    CPPUNIT_ASSERT(ut_is_dimensionless(aDimensionless.mpUnit.get()) != 0);
+
+    // Test that we can't create garbage units
+    UtUnit aGarbage;
+    CPPUNIT_ASSERT(!UtUnit::createUnit("garbage", aGarbage, mpUnitsImpl->mpUnitSystem));
+}
+
 void UnitsTest::testStringExtraction() {
     CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
     CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index c075d00..c1df459 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -19,6 +19,10 @@
 namespace sc {
 namespace units {
 
+namespace test {
+    class UnitsTest;
+}
+
 /*
  * Convenience shared_ptr wrapper for ut_unit, which takes
  * care of dealing with the necessary custom deleter.
@@ -29,6 +33,8 @@ namespace units {
  * wrapper.
  */
 class UtUnit {
+    friend class test::UnitsTest;
+
 private:
     ::boost::shared_ptr< ut_unit > mpUnit;
 
commit 467efa0a76870a87b2e607bcc5e74b2c29e58817
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Thu Feb 5 17:23:50 2015 +0000

    Add factory method for UtUnit creation when parsing.
    
    Change-Id: I1e812fc9f2dfaeccb7a6c65f3739f3be6e206760

diff --git a/sc/source/core/units/utunit.cxx b/sc/source/core/units/utunit.cxx
index 74e28b6..7061658 100644
--- a/sc/source/core/units/utunit.cxx
+++ b/sc/source/core/units/utunit.cxx
@@ -13,6 +13,21 @@
 
 using namespace sc::units;
 
+bool UtUnit::createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boost::shared_ptr< ut_system >& pUTSystem) {
+    OString sUnitStringUTF8 = OUStringToOString(rUnitString, RTL_TEXTENCODING_UTF8);
+
+    UtUnit pParsedUnit(ut_parse(pUTSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
+
+    if (pParsedUnit) {
+        rUnitOut = pParsedUnit;
+        return true;
+    } else {
+        SAL_INFO("sc.units", "error encountered parsing unit \"" << rUnitString << "\": " << getUTStatus());
+        return false;
+    }
+}
+
+
 OUString UtUnit::getString() const {
     char aBuf[200];
     int nChars = ut_format(mpUnit.get(), aBuf, 200, UT_UTF8);
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index a209cf9..c075d00 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -37,6 +37,8 @@ private:
     }
 
 public:
+    static bool createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boost::shared_ptr< ut_system >& pUTSystem);
+
     UtUnit(ut_unit* pUnit = 0):
         mpUnit(pUnit, &freeUt)
     {}
commit d4390310dc0b50c7c839ba761a03b2517685c65d
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Wed Jan 28 18:18:27 2015 +0000

    Implement operators * and / for UtUnit.
    
    This will save more convoluted calls to ut_multiply/divide
    when implementing further opcodes.
    
    Change-Id: I022bd8aad4a8165a68534730447a0c8b9c8f4aba

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 19feff0..adb5d65 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -145,10 +145,10 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             }
             break;
         case ocMul:
-            pOut.reset(ut_multiply(pFirstUnit.get(), pSecondUnit.get()));
+            pOut = pFirstUnit * pSecondUnit;
             break;
         case ocDiv:
-            pOut.reset(ut_divide(pFirstUnit.get(), pSecondUnit.get()));
+            pOut = pFirstUnit / pSecondUnit;
             break;
         default:
             SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 5f77e84..a209cf9 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -62,6 +62,16 @@ public:
     bool operator==(const UtUnit& rUnit) {
         return ut_compare(this->get(), rUnit.get()) == 0;
     }
+
+    UtUnit operator*(const UtUnit& rUnit) {
+        return UtUnit(ut_multiply(this->get(), rUnit.get()));
+    }
+
+    UtUnit operator/(const UtUnit& rUnit) {
+        // the parameter is the right hand side value in the operation,
+        // i.e. we are working with this / rUnit.
+        return UtUnit(ut_divide(this->get(), rUnit.get()));
+    }
 };
 
 }} // namespace sc::units
commit 7953e75717122d23cf26090d00285b3e21a8231c
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Wed Jan 28 17:48:47 2015 +0000

    Implement operator== for UtUnit
    
    (Although this only replaces one call to ut_compare, implementation
     of further opcodes/further functionality will likely require much
     more comparison. We should probably also have an operator* and operator/
     in future.)
    
    Change-Id: Ib31d4dec753823260e9905446ac3d5cd7eb720c8

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index a2c5581..19feff0 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -136,7 +136,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             // Adding and subtracting both require the same units on both sides
             // hence we can just fall through / use the same logic.
         case ocSub:
-            if (ut_compare(pFirstUnit.get(), pSecondUnit.get()) == 0) {
+            if (pFirstUnit == pSecondUnit) {
                 // The two units are identical, hence we can return either.
                 pOut = pFirstUnit;
                 SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 6ad2719..5f77e84 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -58,6 +58,10 @@ public:
     explicit operator bool() const {
         return mpUnit.operator bool();
     }
+
+    bool operator==(const UtUnit& rUnit) {
+        return ut_compare(this->get(), rUnit.get()) == 0;
+    }
 };
 
 }} // namespace sc::units
commit 62db857d4d8e54291ec3919f43730a8ca98561ee
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Tue Jan 27 13:29:21 2015 +0000

    Implement tests for unit verification.
    
    Change-Id: I1fc97b2eeed404e897f4816fe65f05e14150de28

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 290e74a..e8ec0ab 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -9,8 +9,12 @@
 
 #include "unitsimpl.hxx"
 
+#include "formulacell.hxx"
+
 #include "helper/qahelper.hxx"
 
+#include <com/sun/star/util/NumberFormat.hpp>
+
 using namespace sc::units;
 
 // In order to be able to access the private members of UnitsImpl for
@@ -34,19 +38,34 @@ public:
     ::boost::shared_ptr< UnitsImpl > mpUnitsImpl;
 
     void testStringExtraction();
+    void testUnitVerification();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
     CPPUNIT_TEST(testStringExtraction);
+    CPPUNIT_TEST(testUnitVerification);
     CPPUNIT_TEST_SUITE_END();
+
+private:
+    ScDocument *mpDoc;
+    ScDocShellRef m_xDocShRef;
 };
 
 void UnitsTest::setUp() {
     BootstrapFixture::setUp();
 
+    ScDLL::Init();
+    m_xDocShRef = new ScDocShell(
+        SFXMODEL_STANDARD |
+        SFXMODEL_DISABLE_EMBEDDED_SCRIPTS |
+        SFXMODEL_DISABLE_DOCUMENT_RECOVERY);
+
+    mpDoc = &m_xDocShRef->GetDocument();
+
     mpUnitsImpl = UnitsImpl::GetUnits();
 }
 
 void UnitsTest::tearDown() {
+    m_xDocShRef.Clear();
     BootstrapFixture::tearDown();
 }
 
@@ -55,6 +74,111 @@ void UnitsTest::testStringExtraction() {
     CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
 }
 
+void UnitsTest::testUnitVerification() {
+    // Make sure we have at least one tab to work with
+    mpDoc->EnsureTable(0);
+
+    SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
+    sal_uInt32 nKeyCM, nKeyKG, nKeyS, nKeyCM_S;
+
+    // Used to return position of error in input string for PutEntry
+    // -- not needed here.
+    sal_Int32 nCheckPos;
+
+    short nType = css::util::NumberFormat::DEFINED;
+
+    OUString sCM = "#\"cm\"";
+    pFormatter->PutEntry(sCM, nCheckPos, nType, nKeyCM);
+    OUString sKG = "#\"kg\"";
+    pFormatter->PutEntry(sKG, nCheckPos, nType, nKeyKG);
+    OUString sS = "#\"s\"";
+    pFormatter->PutEntry(sS, nCheckPos, nType, nKeyS);
+    OUString sCM_S = "#\"cm/s\"";
+    pFormatter->PutEntry(sCM_S, nCheckPos, nType, nKeyCM_S);
+
+    // 1st column: 10cm, 20cm, 30cm
+    ScAddress address(0, 0, 0);
+    mpDoc->SetNumberFormat(address, nKeyCM);
+    mpDoc->SetValue(address, 10);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyCM);
+    mpDoc->SetValue(address, 20);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyCM);
+    mpDoc->SetValue(address, 30);
+
+    // 2nd column: 1kg, 2kg, 3kg
+    address = ScAddress(1, 0, 0);
+    mpDoc->SetNumberFormat(address, nKeyKG);
+    mpDoc->SetValue(address, 1);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyKG);
+    mpDoc->SetValue(address, 2);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyKG);
+    mpDoc->SetValue(address, 3);
+
+    // 3rd column: 1s, 2s, 3s
+    address = ScAddress(2, 0, 0);
+    mpDoc->SetNumberFormat(address, nKeyS);
+    mpDoc->SetValue(address, 1);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyS);
+    mpDoc->SetValue(address, 2);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyS);
+    mpDoc->SetValue(address, 3);
+
+    // 4th column: 5cm/s
+    address = ScAddress(3, 0, 0);
+    mpDoc->SetNumberFormat(address, nKeyCM_S);
+    mpDoc->SetValue(address, 5);
+
+    ScFormulaCell* pCell;
+    ScTokenArray* pTokens;
+
+    // Test that addition of the same unit is successful
+    address = ScAddress(0, 4, 0);
+    mpDoc->SetFormula(address, "=A1+A2");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test that addition of different units fails
+    address = ScAddress(0, 6, 0);
+    mpDoc->SetFormula(address, "=A1+B1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test that addition and multiplication works (i.e. kg*s+kg*s)
+    address = ScAddress(0, 7, 0);
+    mpDoc->SetFormula(address, "=A1*B1+A2*B2");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test another combination (i.e. cm/s+'cm/s')
+    address = ScAddress(0, 8, 0);
+    mpDoc->SetFormula(address, "=A1/C1+D1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test that another combination fails (cm*kg/s+'cm/s')
+    address = ScAddress(0, 9, 0);
+    mpDoc->SetFormula(address, "=A1*B1/C1+D1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
commit 7150369e125a9ba870dc1cb0c30224fd40d5e47a
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 17:25:33 2015 +0000

    No need to pass in unit system anymore.
    
    getUnitForRef is now a member that can access our unit system
    so we don't need to pass it in.
    
    Change-Id: I31f7ebed98dda3e3573866c36ee84e631c8f6de8

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 13d2ed0..a2c5581 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -201,7 +201,7 @@ OUString UnitsImpl::extractUnitStringForCell(ScAddress& rAddress, ScDocument* pD
 }
 
 UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
-                    ScDocument* pDoc, ::boost::shared_ptr< ut_system > pUnitSystem) {
+                    ScDocument* pDoc) {
     assert(pToken->GetType() == formula::svSingleRef);
 
     ScSingleRefData* pRef = pToken->GetSingleRef();
@@ -220,7 +220,7 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
     // hence we need to manually detect that case and return the dimensionless unit.
     if (sUnitString.getLength() == 0) {
         SAL_INFO("sc.units", "empty unit string: returning dimensionless unit");
-        return UtUnit(ut_get_dimensionless_unit_one(pUnitSystem.get()));
+        return UtUnit(ut_get_dimensionless_unit_one(mpUnitSystem.get()));
     }
 
     SAL_INFO("sc.units", "got unit string [" << sUnitString << "]");
@@ -229,7 +229,7 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
     // TODO: we should probably have a cache of unit strings here to save reparsing
     // on every run?
 
-    UtUnit pUnit(ut_parse(pUnitSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
+    UtUnit pUnit(ut_parse(mpUnitSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
 
     if (!pUnit) {
         SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
@@ -253,7 +253,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
         switch (pToken->GetType()) {
         case formula::svSingleRef:
         {
-            UtUnit pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc, mpUnitSystem));
+            UtUnit pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc));
 
             if (!pUnit) {
                 SAL_INFO("sc.units", "no unit returned for scSingleRef, ut_status: " << getUTStatus());
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index e8845a0..2561e1e 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -67,10 +67,8 @@ private:
     OUString extractUnitStringFromFormat(const OUString& rFormatString);
     OUString extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
-                        const ScAddress& rFormulaAddress,
-                        ScDocument* pDoc,
-                        ::boost::shared_ptr< ut_system > pUnitSystem);
-
+                         const ScAddress& rFormulaAddress,
+                         ScDocument* pDoc);
 };
 
 }} // namespace sc::units
commit 2f94bdd3d2a6aa5617b2f8834f60ae3a347af0e5
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 17:21:50 2015 +0000

    Implement setUp/tearDown for units test.
    
    In preparation for adding more tests.
    
    Change-Id: I63ba844d0b2b39df975abb6d4043e88fba9137dd

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 6a7a08c..290e74a 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -28,6 +28,11 @@ public:
     UnitsTest() {};
     virtual ~UnitsTest() {};
 
+    virtual void setUp() SAL_OVERRIDE;
+    virtual void tearDown() SAL_OVERRIDE;
+
+    ::boost::shared_ptr< UnitsImpl > mpUnitsImpl;
+
     void testStringExtraction();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
@@ -35,11 +40,19 @@ public:
     CPPUNIT_TEST_SUITE_END();
 };
 
-void UnitsTest::testStringExtraction() {
-    ::boost::shared_ptr< UnitsImpl > pUnitsImpl = UnitsImpl::GetUnits();
+void UnitsTest::setUp() {
+    BootstrapFixture::setUp();
+
+    mpUnitsImpl = UnitsImpl::GetUnits();
+}
+
+void UnitsTest::tearDown() {
+    BootstrapFixture::tearDown();
+}
 
-    CPPUNIT_ASSERT(pUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
-    CPPUNIT_ASSERT(pUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
+void UnitsTest::testStringExtraction() {
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
commit e4950c83318b5e9cc0bbf00e0f092ccc72effbae
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 17:13:53 2015 +0000

    Add support for unary opcodes to unit verification.
    
    Change-Id: I802079e6ee61a6807e97cd87df3b4bf3223d9b03

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index a1beed4..13d2ed0 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -81,7 +81,42 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
 
     auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(rOpCode);
 
-    if (nOpCode >= SC_OPCODE_START_BIN_OP &&
+    // TODO: sc/source/core/tool/parclass.cxx has a mapping of opcodes to possible operands, which we
+    // should probably be using in practice.
+
+    if (nOpCode >= SC_OPCODE_START_UN_OP &&
+        nOpCode < SC_OPCODE_STOP_UN_OP) {
+
+        if (!(rUnitStack.size() >= 1)) {
+            SAL_WARN("sc.units", "no units on stack for unary operation");
+            return 0;
+        }
+
+        UtUnit pUnit = rUnitStack.top();
+        rUnitStack.pop();
+
+        switch (rOpCode) {
+        case ocNot:
+            if (!ut_is_dimensionless(pUnit.get())) {
+                return 0;
+            }
+            // We just keep the same unit (in this case no unit) so can
+            // fall through.
+        case ocNeg:
+            // fall through -- same as OcNegSub
+            // It seems the difference is that ocNeg: 'NEG(value)', and ocNegSub: '-value' when
+            // in human readable form.
+        case ocNegSub:
+            // do nothing: since we're just negating the value which doesn't
+            // affect units in any way, we just return the current unit.
+            pOut = pUnit;
+            break;
+        default:
+            // Only the above 3 opcodes are in the range we have tested for previously
+            // (...START_UN_OP to ...STOP_UN_OP).
+            assert(false);
+        }
+    } else if (nOpCode >= SC_OPCODE_START_BIN_OP &&
         nOpCode < SC_OPCODE_STOP_BIN_OP) {
 
         if (!(rUnitStack.size() >= 2)) {
@@ -120,6 +155,8 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             assert(false);
         }
 
+    } else {
+        SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
     }
     // TODO: else if unary, or no params, or ...
     // TODO: implement further sensible opcode handling
commit 85715a8d9fbd4b15dcfc2c3f9af2ea33328b7657
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 16:41:29 2015 +0000

    Allow access to whole stack for opcode processing.
    
    An opcode can operate on 0, 1, 2 or n values/units, hence
    we need to be able to access the whole stack in order to be
    able to implement support for all opcodes.
    
    Change-Id: I145d430ef412825ac4437a5049c955b2d7eb0bfb

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 4c94df07..a1beed4 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -21,7 +21,6 @@
 #include <svl/zformat.hxx>
 
 #include <boost/scoped_array.hpp>
-#include <stack>
 
 using namespace formula;
 using namespace sc;
@@ -77,36 +76,55 @@ UnitsImpl::~UnitsImpl() {
     // (i.e. if udunits can't handle being used across threads)?
 }
 
-UtUnit UnitsImpl::getOutputUnitsForOpCode(const UtUnit& pFirstUnit, const UtUnit& pSecondUnit, const OpCode& rOpCode) {
+UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpCode& rOpCode) {
     UtUnit pOut;
 
-    switch (rOpCode) {
-    case ocAdd:
-        // Adding and subtracting both require the same units on both sides
-        // hence we can just fall through / use the same logic.
-    case ocSub:
-        if (ut_compare(pFirstUnit.get(), pSecondUnit.get()) == 0) {
-            // The two units are identical, hence we can return either.
-            pOut = pFirstUnit;
-            SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
-        } else {
-            // TODO: notify/link UI.
+    auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(rOpCode);
+
+    if (nOpCode >= SC_OPCODE_START_BIN_OP &&
+        nOpCode < SC_OPCODE_STOP_BIN_OP) {
+
+        if (!(rUnitStack.size() >= 2)) {
+            SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
+            // TODO: what should we be telling the user in this case? Can this even happen (i.e.
+            // should we just be asserting here?)
+            return 0;
+        }
+
+        UtUnit pSecondUnit = rUnitStack.top();
+        rUnitStack.pop();
+        UtUnit pFirstUnit = rUnitStack.top();
+        rUnitStack.pop();
+
+        switch (rOpCode) {
+        case ocAdd:
+            // Adding and subtracting both require the same units on both sides
+            // hence we can just fall through / use the same logic.
+        case ocSub:
+            if (ut_compare(pFirstUnit.get(), pSecondUnit.get()) == 0) {
+                // The two units are identical, hence we can return either.
+                pOut = pFirstUnit;
+                SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
+            } else {
+                // TODO: notify/link UI.
+            }
+            break;
+        case ocMul:
+            pOut.reset(ut_multiply(pFirstUnit.get(), pSecondUnit.get()));
+            break;
+        case ocDiv:
+            pOut.reset(ut_divide(pFirstUnit.get(), pSecondUnit.get()));
+            break;
+        default:
+            SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
+            assert(false);
         }
-        break;
-    case ocMul:
-        pOut.reset(ut_multiply(pFirstUnit.get(), pSecondUnit.get()));
-        break;
-    case ocDiv:
-        pOut.reset(ut_divide(pFirstUnit.get(), pSecondUnit.get()));
-        break;
-    default:
-        SAL_INFO("sc.units", "unit verification not supported for opcode: " << static_cast<std::underlying_type<const OpCode>::type>(rOpCode));
-        assert(false);
+
     }
+    // TODO: else if unary, or no params, or ...
+    // TODO: implement further sensible opcode handling
 
     return pOut;
-
-// TODO: implement further sensible opcode handling
 }
 
 OUString UnitsImpl::extractUnitStringFromFormat(const OUString& rFormatString) {
@@ -216,17 +234,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
         }
         case formula::svByte:
         {
-            if (!(aUnitStack.size() >= 2)) {
-                SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
-                return false;
-            }
-
-            UtUnit pSecondUnit = aUnitStack.top();
-            aUnitStack.pop();
-            UtUnit pFirstUnit = aUnitStack.top();
-            aUnitStack.pop();
-
-            UtUnit pOut = getOutputUnitsForOpCode(pFirstUnit, pSecondUnit, pToken->GetOpCode());
+            UtUnit pOut = getOutputUnitsForOpCode(aUnitStack, pToken->GetOpCode());
 
             // A null unit indicates either invalid units and/or other erronous input
             // i.e. is an indication that getOutputUnitsForOpCode failed.
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 82a9b0a..e8845a0 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -23,6 +23,8 @@
 #include <units.hxx>
 #include "utunit.hxx"
 
+#include <stack>
+
 namespace formula {
     class FormulaToken;
 }
@@ -61,7 +63,7 @@ public:
     virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) SAL_OVERRIDE;
 
 private:
-    UtUnit getOutputUnitsForOpCode(const UtUnit& pFirstUnit, const UtUnit& pSecondUnit, const OpCode& rOpCode);
+    UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
     OUString extractUnitStringFromFormat(const OUString& rFormatString);
     OUString extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
commit 9bb2df441dfc975264cd8b31313521028fcdffec
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 16:25:26 2015 +0000

    Update UnitsTest to use the refactored design.
    
    Change-Id: I37c8d0c44612519e01e7c62353c395fbe4cf3116

diff --git a/sc/CppunitTest_sc_units.mk b/sc/CppunitTest_sc_units.mk
index 73fba99..241836b 100644
--- a/sc/CppunitTest_sc_units.mk
+++ b/sc/CppunitTest_sc_units.mk
@@ -78,6 +78,7 @@ $(eval $(call gb_CppunitTest_set_include,sc_units,\
     -I$(SRCDIR)/sc/source/ui/inc \
     -I$(SRCDIR)/sc/source/core/inc \
     -I$(SRCDIR)/sc/inc \
+	-I$(SRCDIR)/sc/source/core/units \
     $$(INCLUDE) \
 ))
 
diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index fb091e4..6a7a08c 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -7,12 +7,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-#include "units.hxx"
+#include "unitsimpl.hxx"
 
 #include "helper/qahelper.hxx"
 
+using namespace sc::units;
+
+// In order to be able to access the private members of UnitsImpl for
+// testing, we need to be a friend of UnitsImpl. For this to work
+// UnitsTest can't be a member of the anonymous namespace hence the
+// need to use a namespace here.
+namespace sc {
+namespace units {
+namespace test {
+
 class UnitsTest:
-    public test::BootstrapFixture
+    public ::test::BootstrapFixture
 {
 public:
     UnitsTest() {};
@@ -26,12 +36,16 @@ public:
 };
 
 void UnitsTest::testStringExtraction() {
-    CPPUNIT_ASSERT(extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
-    CPPUNIT_ASSERT(extractUnitStringFromFormat("#\"cm\"") == "cm");
+    ::boost::shared_ptr< UnitsImpl > pUnitsImpl = UnitsImpl::GetUnits();
+
+    CPPUNIT_ASSERT(pUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
+    CPPUNIT_ASSERT(pUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
 
+}}} // namespace sc::units::test
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/qa/unit/units.hxx b/sc/qa/unit/units.hxx
deleted file mode 100644
index 16861a96..0000000
--- a/sc/qa/unit/units.hxx
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- 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/.
- */
-
-#ifndef INCLUDED_SC_QA_UNIT_UNITS_HXX
-#define INCLUDED_SC_QA_UNIT_UNITS_HXX
-
-#include <rtl/ustring.hxx>
-
-// Forward declarations of our local functions that need testing
-// here.
-OUString extractUnitStringFromFormat(const OUString& rFormatString);
-
-#endif
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index af8d679..4c94df07 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -7,7 +7,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  */
-#include "unitsImpl.hxx"
+#include "unitsimpl.hxx"
 
 #include "util.hxx"
 
@@ -31,7 +31,7 @@ using namespace std;
 ::osl::Mutex sc::units::UnitsImpl::ourSingletonMutex;
 ::boost::weak_ptr< UnitsImpl > sc::units::UnitsImpl::ourUnits;
 
-::boost::shared_ptr< Units > UnitsImpl::GetUnits() {
+::boost::shared_ptr< UnitsImpl > UnitsImpl::GetUnits() {
     osl::MutexGuard aGuard(ourSingletonMutex);
     boost::shared_ptr< UnitsImpl > pUnits = ourUnits.lock();
 
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 7ea1700..82a9b0a 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -32,7 +32,13 @@ struct ut_system;
 namespace sc {
 namespace units {
 
+namespace test {
+    class UnitsTest;
+}
+
 class UnitsImpl: public Units {
+    friend class test::UnitsTest;
+
 private:
     static ::osl::Mutex ourSingletonMutex;
     static ::boost::weak_ptr< UnitsImpl > ourUnits;
@@ -47,7 +53,7 @@ private:
     }
 
 public:
-    static ::boost::shared_ptr< Units > GetUnits();
+    static ::boost::shared_ptr< UnitsImpl > GetUnits();
 
     UnitsImpl();
     virtual ~UnitsImpl();
commit 9cb0923b0112986bf486063740d76ffb895a93a0
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Jan 26 15:52:30 2015 +0000

    Split up / refactor unit verification.
    
    Change-Id: Ic188cf4046ed6d3f1e705f7fe97a26a7914ab4d8

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 4fcc25b..778415c 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -689,6 +689,9 @@ $(call gb_Library_add_exception_objects,sc,\
 ifeq ($(ENABLE_CALC_UNITVERIFICATION),TRUE)
 $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/core/units/units \
+	sc/source/core/units/unitsimpl \
+	sc/source/core/units/util \
+	sc/source/core/units/utunit \
 ))
 endif
 
diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx
index d3aec50..cfeae9e 100644
--- a/sc/inc/units.hxx
+++ b/sc/inc/units.hxx
@@ -11,45 +11,26 @@
 #define INCLUDED_SC_INC_UNITS_HXX
 
 #include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <osl/mutex.hxx>
-
-struct ut_system;
 
 class ScAddress;
 class ScDocument;
 class ScTokenArray;
 
 namespace sc {
+namespace units {
 
-/*
- * We implement this as a singleton which automatically
- * cleans itself up thanks to the use of shared and weak
- * pointers.
- */
-class Units {
-private:
-    // A scoped_ptr would be more appropriate, however
-    // we require a custom deleter which scoped_ptr doesn't
-    // offer.
-    ::boost::shared_ptr< ut_system > mpUnitSystem;
-
-    Units();
-
-    static ::osl::Mutex ourSingletonMutex;
-    static ::boost::weak_ptr< Units > ourUnits;
+class UnitsImpl;
 
+class Units {
 public:
     static ::boost::shared_ptr< Units > GetUnits();
 
-    ~Units();
-
-    bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc);
+    virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) = 0;
 
+    virtual ~Units() {}
 };
 
-} // namespace sc
+}} // namespace sc::units
 
 #endif // INCLUDED_SC_INC_UNITS_HXX
 
diff --git a/sc/source/core/units/units.cxx b/sc/source/core/units/units.cxx
index f7b12b9..190aa96 100644
--- a/sc/source/core/units/units.cxx
+++ b/sc/source/core/units/units.cxx
@@ -7,335 +7,14 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  */
-#include "units.hxx"
+#include <units.hxx>
+#include "unitsimpl.hxx"
 
-#include "document.hxx"
-#include "refdata.hxx"
-#include "tokenarray.hxx"
-
-#include <osl/file.hxx>
-#include <osl/mutex.hxx>
-#include <rtl/bootstrap.hxx>
-#include <svl/zformat.hxx>
-
-#include <boost/scoped_array.hpp>
-#include <stack>
-
-#include <udunits2.h>
-
-using namespace formula;
 using namespace sc;
-using namespace std;
-
-::osl::Mutex sc::Units::ourSingletonMutex;
-::boost::weak_ptr< Units > sc::Units::ourUnits;
-
-OUString dumpUTStatus() {
-    switch(ut_get_status()) {
-    case UT_SUCCESS:
-        return "UT_SUCCESS: successful!";
-    case UT_BAD_ARG:
-        return "UT_BAD_ARG: invalid argument";
-    case UT_EXISTS:
-        return "UT_EXISTS: unit/prefix/identifier already exists";
-    case UT_NO_UNIT:
-        return "UT_NO_UNIT: no such unit exists";
-    case UT_OS:
-        return "UT_OS: operating system error (check errno?)";
-    case UT_NOT_SAME_SYSTEM:
-        return "UT_NOT_SAME_SYSTEM: units not in same unit system";
-    case UT_MEANINGLESS:
-        return "UT_MEANINGLESS: operation is meaningless";
-    case UT_NO_SECOND:
-        return "UT_NO_SECOND: no unit named second";
-    case UT_VISIT_ERROR:
-        return "UT_VISIT_ERROR";
-    case UT_CANT_FORMAT:
-        return "UT_CANT_FORMAT";
-    case UT_SYNTAX:
-        return "UT_SYNTAX: syntax error in unit string";
-    case UT_UNKNOWN:
-        return "UT_UNKNOWN: unknown unit encountered";
-    case UT_OPEN_ARG:
-        return "UT_OPEN_ARG: can't open specified unit database (arg)";
-    case UT_OPEN_ENV:
-        return "UT_OPEN_ENV: can't open specified unit databse (env)";
-    case UT_OPEN_DEFAULT:
-        return "UT_OPEN_DEFAULT: can't open default unit database";
-    default:
-        return "other (unpspecified) error encountered";
-    }
-}
-
-class UnitP:
-    public ::boost::shared_ptr< ut_unit > {
-public:
-    UnitP(ut_unit* pUnit):
-        boost::shared_ptr< ut_unit >(pUnit, &freeUt)
-        {}
-
-    UnitP():
-        boost::shared_ptr< ut_unit >(0, &freeUt)
-        {}
-
-    void reset(ut_unit* pUnit) {
-        boost::shared_ptr< ut_unit >::reset(pUnit, &freeUt);
-    }
-
-    OUString getString() const {
-        char aBuf[200];
-        int nChars = ut_format(this->get(), aBuf, 200, UT_UTF8);
-        if (nChars == -1) {
-            SAL_INFO("sc.units", "couldn't format unit: " << dumpUTStatus());
-            // Placeholder for unformattable strings.
-            return "?";
-        }
-
-        // If the output doesn't fit in the buffer, ut_format doesn't write
-        // a terminating null. However for any output we have the correct length
-        // as returned by ut_format, which is the easiest way to ensure the OString
-        // constructor will always work correctly. (Alternatively we could retry with
-        // a larger buffer, however this method is purely for debugging purposes for now.)
-
-        return OUString(aBuf, nChars, RTL_TEXTENCODING_UTF8);
-    }
-
-private:
-    static void freeUt(ut_unit* pUnit) {
-        ut_free(pUnit);
-    }
-};
-
-UnitP getOutputUnitsForOpCode(const UnitP& pFirstUnit, const UnitP& pSecondUnit, const OpCode& rOpCode) {
-    UnitP pOut;
-
-    switch (rOpCode) {
-    case ocAdd:
-        // Adding and subtracting both require the same units on both sides
-        // hence we can just fall through / use the same logic.
-    case ocSub:
-        if (ut_compare(pFirstUnit.get(), pSecondUnit.get()) == 0) {
-            // The two units are identical, hence we can return either.
-            pOut = pFirstUnit;
-            SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
-        } else {
-            // TODO: notify/link UI.
-        }
-        break;
-    case ocMul:
-        pOut.reset(ut_multiply(pFirstUnit.get(), pSecondUnit.get()));
-        break;
-    case ocDiv:
-        pOut.reset(ut_divide(pFirstUnit.get(), pSecondUnit.get()));
-        break;
-    default:
-        SAL_INFO("sc.units", "unit verification not supported for opcode: " << static_cast<std::underlying_type<const OpCode>::type>(rOpCode));
-        assert(false);
-    }
-
-    return pOut;
-
-// TODO: implement further sensible opcode handling
-}
-
-static void freeUtSystem(ut_system* pSystem) {
-    ut_free_system(pSystem);
-}
-
-Units::Units() {
-    SAL_INFO("sc.units", "initialising udunits2");
-
-    // System udunits will (/should) be able to find it's unit database
-    // itself -- however for bundled udunits we always need to find the
-    // correct relative path within our LO installation.
-#ifdef USING_SYSTEM_UDUNITS
-    const sal_Char* pPath = 0;
-#else
-    OUString sDBURL("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/udunits2/udunits2.xml");
-    ::rtl::Bootstrap::expandMacros(sDBURL);
-    OUString sDBPath;
-    ::osl::FileBase::getSystemPathFromFileURL(sDBURL, sDBPath);
-
-    OString sDBPathOut = OUStringToOString(sDBPath, RTL_TEXTENCODING_ASCII_US);
-    const sal_Char* pPath = sDBPathOut.getStr();
-#endif
-
-    mpUnitSystem = boost::shared_ptr< ut_system >( ut_read_xml( pPath ),
-                                                   &freeUtSystem );
-
-    SAL_INFO("sc.units", "udunits2 initialised");
-}
-
-Units::~Units() {
-    // We only arrive here if all shared_ptr's to our Units get
-    // disposed. In this case the weak_ptr is already cleared,
-    // and any new calls to GetUnits don't need to care at what
-    // stage of destruction we are?
-
-    // We might need to lock on our singletonMutex if we can't
-    // load the same unit system multiple times in memory
-    // (i.e. if udunits can't handle being used across threads)?
-}
+using namespace sc::units;
 
 boost::shared_ptr< Units > Units::GetUnits() {
-    osl::MutexGuard aGuard(ourSingletonMutex);
-    boost::shared_ptr< Units > pUnits = ourUnits.lock();
-
-    if (!pUnits) {
-        pUnits.reset( new Units() );
-        ourUnits = pUnits;
-    }
-    return pUnits;
-}
-
-OUString extractUnitStringFromFormat(const OUString& rFormatString) {
-    // TODO: decide what we do for different subformats? Simplest solution
-    // would be to not allow unit storage for multiple subformats.
-    // TODO: we should check the number of subformats here in future?
-
-    // TODO: use proper string processing routines?
-
-    sal_Int32 nPos = rFormatString.getLength() - 1;
-
-    // Only iterate if we have a string item at the end of our format string
-    if (rFormatString[nPos] == '\"') {
-       // TODO: deal with escaped strings? (Does that exist in these?)
-        while (rFormatString[--nPos] != '\"') {
-            if (nPos == 0) {
-                // TODO: plug into our error reporting here to return bad escaping?
-                return "";
-            }
-        }
-    } else { // otherwise we have no units for this cell
-        return "";
-    }
-
-    // Ensure that the parentheses are NOT included in our unit string.
-    return rFormatString.copy(nPos + 1, rFormatString.getLength() - nPos - 2);
-}
-
-
-OUString extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc) {
-    sal_uInt32 nFormat = pDoc->GetNumberFormat(rAddress);
-    const SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
-    const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
-    const OUString& rFormatString = pFormat->GetFormatstring();
-
-    return extractUnitStringFromFormat(rFormatString);
-}
-
-// get units for single ref -- use format, but then fall back to header?
-// or have some sort of marker per cell? (Per cell range -- linked to mdds?)
-
-UnitP getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
-                    ScDocument* pDoc, ::boost::shared_ptr< ut_system > pUnitSystem) {
-    assert(pToken->GetType() == formula::svSingleRef);
-
-    ScSingleRefData* pRef = pToken->GetSingleRef();
-    assert(pRef);
-
-    // Addresses can/will be relative to the formula, for extracting
-    // units however we will need to get the absolute address (i.e.
-    // by adding the current address to the relative formula address).
-    ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
-
-    // udunits requires strings to be trimmed before parsing -- it's easiest to do this
-    // using the OUString utils (as opposed to using ut_trim once we have a c string.
-    OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc).trim();
-
-    // empty string == dimensionless unit. ut_parse returns an error for an empty string
-    // hence we need to manually detect that case and return the dimensionless unit.
-    if (sUnitString.getLength() == 0) {
-        SAL_INFO("sc.units", "empty unit string: returning dimensionless unit");
-        return UnitP(ut_get_dimensionless_unit_one(pUnitSystem.get()));
-    }
-
-    SAL_INFO("sc.units", "got unit string [" << sUnitString << "]");
-    OString sUnitStringUTF8 = OUStringToOString(sUnitString, RTL_TEXTENCODING_UTF8);
-
-    // TODO: we should probably have a cache of unit strings here to save reparsing
-    // on every run?
-
-    UnitP pUnit(ut_parse(pUnitSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
-
-    if (!pUnit) {
-        SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
-        SAL_INFO("sc.units", "error encountered: " << dumpUTStatus());
-    }
-
-    return pUnit;
+    return UnitsImpl::GetUnits();
 }
 
-// getUnitForRef: check format -> if not in format, use more complicated method? (Format overrides header definition)
-
-bool Units::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) {
-#if DEBUG_FORMULA_COMPILER
-    pArray->Dump();
-#endif
-
-    stack< UnitP > aUnitStack;
-
-    FormulaToken* pToken = pArray->FirstRPN();
-
-    while (pToken != 0) {
-        switch (pToken->GetType()) {
-        case formula::svSingleRef:
-        {
-            UnitP pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc, mpUnitSystem));
-
-            if (!pUnit) {
-                SAL_INFO("sc.units", "no unit returned for scSingleRef, ut_status: " << dumpUTStatus());
-
-                // This only happens in case of parsing (or system) errors.
-                // However maybe we should be returning "unverified" for
-                // unparseable formulas?
-                // (or even have a "can't be verified" state too?)
-                // see below for more.
-                return false;
-            }
-
-            aUnitStack.push(pUnit);
-            break;
-        }
-        case formula::svByte:
-        {
-            if (!(aUnitStack.size() >= 2)) {
-                SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
-                return false;
-            }
-
-            UnitP pSecondUnit = aUnitStack.top();
-            aUnitStack.pop();
-            UnitP pFirstUnit = aUnitStack.top();
-            aUnitStack.pop();
-
-            UnitP pOut = getOutputUnitsForOpCode(pFirstUnit, pSecondUnit, pToken->GetOpCode());
-
-            // A null unit indicates either invalid units and/or other erronous input
-            // i.e. is an indication that getOutputUnitsForOpCode failed.
-            if (pOut) {
-                aUnitStack.push(pOut);
-            } else {
-                return false;
-            }
-
-            break;
-        }
-        default:
-            // We can't parse any other types of tokens yet, so assume that the formula
-            // was correct.
-            // TODO: maybe we should have a "unverified" return state instead?
-            SAL_WARN("sc.units", "Unrecognised token type " << pToken->GetType());
-            return true;
-        }
-
-        pToken = pArray->NextRPN();
-    }
-
-    // TODO: only fail if actual parsing fails?
-
-    return true;
-}
-
-
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
new file mode 100644
index 0000000..af8d679
--- /dev/null
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -0,0 +1,258 @@
+/* -*- 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 "unitsImpl.hxx"
+
+#include "util.hxx"
+
+#include "document.hxx"
+#include "refdata.hxx"
+#include "tokenarray.hxx"
+
+#include <osl/file.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/bootstrap.hxx>
+#include <svl/zformat.hxx>
+
+#include <boost/scoped_array.hpp>
+#include <stack>
+
+using namespace formula;
+using namespace sc;
+using namespace sc::units;
+using namespace std;
+
+::osl::Mutex sc::units::UnitsImpl::ourSingletonMutex;
+::boost::weak_ptr< UnitsImpl > sc::units::UnitsImpl::ourUnits;
+
+::boost::shared_ptr< Units > UnitsImpl::GetUnits() {
+    osl::MutexGuard aGuard(ourSingletonMutex);
+    boost::shared_ptr< UnitsImpl > pUnits = ourUnits.lock();
+
+    if (!pUnits) {
+        pUnits.reset( new UnitsImpl() );
+        ourUnits = pUnits;
+    }
+    return pUnits;
+}
+
+UnitsImpl::UnitsImpl() {
+    SAL_INFO("sc.units", "initialising udunits2");
+
+    // System udunits will (/should) be able to find it's unit database
+    // itself -- however for bundled udunits we always need to find the
+    // correct relative path within our LO installation.
+#ifdef USING_SYSTEM_UDUNITS
+    const sal_Char* pPath = 0;
+#else
+    OUString sDBURL("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/udunits2/udunits2.xml");
+    ::rtl::Bootstrap::expandMacros(sDBURL);
+    OUString sDBPath;
+    ::osl::FileBase::getSystemPathFromFileURL(sDBURL, sDBPath);
+
+    OString sDBPathOut = OUStringToOString(sDBPath, RTL_TEXTENCODING_ASCII_US);
+    const sal_Char* pPath = sDBPathOut.getStr();
+#endif
+
+    mpUnitSystem = boost::shared_ptr< ut_system >( ut_read_xml( pPath ),
+                                                   &freeUtSystem );
+
+    SAL_INFO("sc.units", "udunits2 initialised");
+}
+
+UnitsImpl::~UnitsImpl() {
+    // We only arrive here if all shared_ptr's to our Units get
+    // disposed. In this case the weak_ptr is already cleared,
+    // and any new calls to GetUnits don't need to care at what
+    // stage of destruction we are?
+
+    // We might need to lock on our singletonMutex if we can't
+    // load the same unit system multiple times in memory
+    // (i.e. if udunits can't handle being used across threads)?
+}
+
+UtUnit UnitsImpl::getOutputUnitsForOpCode(const UtUnit& pFirstUnit, const UtUnit& pSecondUnit, const OpCode& rOpCode) {
+    UtUnit pOut;
+
+    switch (rOpCode) {
+    case ocAdd:
+        // Adding and subtracting both require the same units on both sides
+        // hence we can just fall through / use the same logic.
+    case ocSub:
+        if (ut_compare(pFirstUnit.get(), pSecondUnit.get()) == 0) {
+            // The two units are identical, hence we can return either.
+            pOut = pFirstUnit;
+            SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit.getString());
+        } else {
+            // TODO: notify/link UI.
+        }
+        break;
+    case ocMul:
+        pOut.reset(ut_multiply(pFirstUnit.get(), pSecondUnit.get()));
+        break;
+    case ocDiv:
+        pOut.reset(ut_divide(pFirstUnit.get(), pSecondUnit.get()));
+        break;
+    default:
+        SAL_INFO("sc.units", "unit verification not supported for opcode: " << static_cast<std::underlying_type<const OpCode>::type>(rOpCode));
+        assert(false);
+    }
+
+    return pOut;
+
+// TODO: implement further sensible opcode handling
+}
+
+OUString UnitsImpl::extractUnitStringFromFormat(const OUString& rFormatString) {
+    // TODO: decide what we do for different subformats? Simplest solution
+    // would be to not allow unit storage for multiple subformats.
+    // TODO: we should check the number of subformats here in future?
+
+    // TODO: use proper string processing routines?
+
+    sal_Int32 nPos = rFormatString.getLength() - 1;
+
+    // Only iterate if we have a string item at the end of our format string
+    if (rFormatString[nPos] == '\"') {
+       // TODO: deal with escaped strings? (Does that exist in these?)
+        while (rFormatString[--nPos] != '\"') {
+            if (nPos == 0) {
+                // TODO: plug into our error reporting here to return bad escaping?
+                return "";
+            }
+        }
+    } else { // otherwise we have no units for this cell
+        return "";
+    }
+
+    // Ensure that the parentheses are NOT included in our unit string.
+    return rFormatString.copy(nPos + 1, rFormatString.getLength() - nPos - 2);
+}
+
+
+OUString UnitsImpl::extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc) {
+    sal_uInt32 nFormat = pDoc->GetNumberFormat(rAddress);
+    const SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
+    const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
+    const OUString& rFormatString = pFormat->GetFormatstring();
+
+    return extractUnitStringFromFormat(rFormatString);
+}
+
+UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
+                    ScDocument* pDoc, ::boost::shared_ptr< ut_system > pUnitSystem) {
+    assert(pToken->GetType() == formula::svSingleRef);
+
+    ScSingleRefData* pRef = pToken->GetSingleRef();
+    assert(pRef);
+
+    // Addresses can/will be relative to the formula, for extracting
+    // units however we will need to get the absolute address (i.e.
+    // by adding the current address to the relative formula address).
+    ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
+
+    // udunits requires strings to be trimmed before parsing -- it's easiest to do this
+    // using the OUString utils (as opposed to using ut_trim once we have a c string.
+    OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc).trim();
+
+    // empty string == dimensionless unit. ut_parse returns an error for an empty string
+    // hence we need to manually detect that case and return the dimensionless unit.
+    if (sUnitString.getLength() == 0) {
+        SAL_INFO("sc.units", "empty unit string: returning dimensionless unit");
+        return UtUnit(ut_get_dimensionless_unit_one(pUnitSystem.get()));
+    }
+
+    SAL_INFO("sc.units", "got unit string [" << sUnitString << "]");
+    OString sUnitStringUTF8 = OUStringToOString(sUnitString, RTL_TEXTENCODING_UTF8);
+
+    // TODO: we should probably have a cache of unit strings here to save reparsing
+    // on every run?
+
+    UtUnit pUnit(ut_parse(pUnitSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8));
+
+    if (!pUnit) {
+        SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
+        SAL_INFO("sc.units", "error encountered: " << getUTStatus());
+    }
+
+    return pUnit;
+}
+
+// getUnitForRef: check format -> if not in format, use more complicated method? (Format overrides header definition)
+bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) {
+#if DEBUG_FORMULA_COMPILER
+    pArray->Dump();
+#endif
+
+    stack< UtUnit > aUnitStack;
+
+    FormulaToken* pToken = pArray->FirstRPN();
+
+    while (pToken != 0) {
+        switch (pToken->GetType()) {
+        case formula::svSingleRef:
+        {
+            UtUnit pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc, mpUnitSystem));
+
+            if (!pUnit) {
+                SAL_INFO("sc.units", "no unit returned for scSingleRef, ut_status: " << getUTStatus());
+
+                // This only happens in case of parsing (or system) errors.
+                // However maybe we should be returning "unverified" for
+                // unparseable formulas?
+                // (or even have a "can't be verified" state too?)
+                // see below for more.
+                return false;
+            }
+
+            aUnitStack.push(pUnit);
+            break;
+        }
+        case formula::svByte:
+        {
+            if (!(aUnitStack.size() >= 2)) {
+                SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
+                return false;
+            }
+
+            UtUnit pSecondUnit = aUnitStack.top();
+            aUnitStack.pop();
+            UtUnit pFirstUnit = aUnitStack.top();
+            aUnitStack.pop();
+
+            UtUnit pOut = getOutputUnitsForOpCode(pFirstUnit, pSecondUnit, pToken->GetOpCode());
+
+            // A null unit indicates either invalid units and/or other erronous input
+            // i.e. is an indication that getOutputUnitsForOpCode failed.
+            if (pOut) {
+                aUnitStack.push(pOut);
+            } else {
+                return false;
+            }
+
+            break;
+        }
+        default:
+            // We can't parse any other types of tokens yet, so assume that the formula
+            // was correct.
+            // TODO: maybe we should have a "unverified" return state instead?
+            SAL_WARN("sc.units", "Unrecognised token type " << pToken->GetType());
+            return true;
+        }
+
+        pToken = pArray->NextRPN();
+    }
+
+    // TODO: only fail if actual parsing fails?
+
+    return true;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
new file mode 100644
index 0000000..7ea1700
--- /dev/null
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -0,0 +1,72 @@
+/* -*- 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/.
+ *
+ */
+#ifndef INCLUDED_SC_SOURCE_CORE_UNITS_UNITSIMPL_HXX
+#define INCLUDED_SC_SOURCE_CORE_UNITS_UNITSIMPL_HXX
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <formula/opcode.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/ustring.hxx>
+
+#include <udunits2.h>
+
+#include <units.hxx>
+#include "utunit.hxx"
+
+namespace formula {
+    class FormulaToken;
+}
+
+struct ut_system;
+
+namespace sc {
+namespace units {
+
+class UnitsImpl: public Units {
+private:
+    static ::osl::Mutex ourSingletonMutex;
+    static ::boost::weak_ptr< UnitsImpl > ourUnits;
+
+    // A scoped_ptr would be more appropriate, however
+    // we require a custom deleter which scoped_ptr doesn't
+    // offer.
+    ::boost::shared_ptr< ut_system > mpUnitSystem;
+
+    static void freeUtSystem(ut_system* pSystem) {
+        ut_free_system(pSystem);
+    }
+
+public:
+    static ::boost::shared_ptr< Units > GetUnits();
+
+    UnitsImpl();
+    virtual ~UnitsImpl();
+
+    virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) SAL_OVERRIDE;
+
+private:
+    UtUnit getOutputUnitsForOpCode(const UtUnit& pFirstUnit, const UtUnit& pSecondUnit, const OpCode& rOpCode);
+    OUString extractUnitStringFromFormat(const OUString& rFormatString);
+    OUString extractUnitStringForCell(ScAddress& rAddress, ScDocument* pDoc);
+    UtUnit getUnitForRef(formula::FormulaToken* pToken,
+                        const ScAddress& rFormulaAddress,
+                        ScDocument* pDoc,

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list