/* -*- 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 <config_features.h>

#include <calc.hxx>
#include <cfloat>
#include <climits>
#include <memory>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <cstdlib>
#include <dbfld.hxx>
#include <dbmgr.hxx>
#include <docfld.hxx>
#include <docstat.hxx>
#include <doc.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentStatistics.hxx>
#include <editeng/langitem.hxx>
#include <editeng/scripttypeitem.hxx>
#include <editeng/unolingu.hxx>
#include <expfld.hxx>
#include <hintids.hxx>
#include <osl/diagnose.hxx>
#include <rtl/math.hxx>
#include <shellres.hxx>
#include <svl/zforlist.hxx>
#include <swmodule.hxx>
#include <swtypes.hxx>
#include <unotools/charclass.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/useroptions.hxx>
#include <usrfld.hxx>
#include <viewsh.hxx>

using namespace ::com::sun::star;

// shortcut
#define RESOURCE SwViewShell::GetShellRes()

const sal_Char sCalc_Add[]  =   "add";
const sal_Char sCalc_Sub[]  =   "sub";
const sal_Char sCalc_Mul[]  =   "mul";
const sal_Char sCalc_Div[]  =   "div";
const sal_Char sCalc_Phd[]  =   "phd";
const sal_Char sCalc_Sqrt[] =   "sqrt";
const sal_Char sCalc_Pow[]  =   "pow";
const sal_Char sCalc_Or[]   =   "or";
const sal_Char sCalc_Xor[]  =   "xor";
const sal_Char sCalc_And[]  =   "and";
const sal_Char sCalc_Not[]  =   "not";
const sal_Char sCalc_Eq[]   =   "eq";
const sal_Char sCalc_Neq[]  =   "neq";
const sal_Char sCalc_Leq[]  =   "leq";
const sal_Char sCalc_Geq[]  =   "geq";
const sal_Char sCalc_L[]    =   "l";
const sal_Char sCalc_G[]    =   "g";
const sal_Char sCalc_Sum[]  =   "sum";
const sal_Char sCalc_Mean[] =   "mean";
const sal_Char sCalc_Min[]  =   "min";
const sal_Char sCalc_Max[]  =   "max";
const sal_Char sCalc_Sin[]  =   "sin";
const sal_Char sCalc_Cos[]  =   "cos";
const sal_Char sCalc_Tan[]  =   "tan";
const sal_Char sCalc_Asin[] =   "asin";
const sal_Char sCalc_Acos[] =   "acos";
const sal_Char sCalc_Atan[] =   "atan";
const sal_Char sCalc_Round[]=   "round";
const sal_Char sCalc_Date[] =   "date";

// ATTENTION: sorted list of all operators
struct CalcOp
{
    union{
        const sal_Char* pName;
        const OUString* pUName;
    };
    SwCalcOper eOp;
};

CalcOp const aOpTable[] = {
/* ACOS */    {{sCalc_Acos},       CALC_ACOS},  // Arc cosine
/* ADD */     {{sCalc_Add},        CALC_PLUS},  // Addition
/* AND */     {{sCalc_And},        CALC_AND},   // log. AND
/* ASIN */    {{sCalc_Asin},       CALC_ASIN},  // Arc sine
/* ATAN */    {{sCalc_Atan},       CALC_ATAN},  // Arc tangent
/* COS */     {{sCalc_Cos},        CALC_COS},   // Cosine
/* DATE */    {{sCalc_Date},       CALC_DATE},  // Date
/* DIV */     {{sCalc_Div},        CALC_DIV},   // Division
/* EQ */      {{sCalc_Eq},         CALC_EQ},    // Equality
/* G */       {{sCalc_G},          CALC_GRE},   // Greater than
/* GEQ */     {{sCalc_Geq},        CALC_GEQ},   // Greater or equal
/* L */       {{sCalc_L},          CALC_LES},   // Less than
/* LEQ */     {{sCalc_Leq},        CALC_LEQ},   // Less or equal
/* MAX */     {{sCalc_Max},        CALC_MAX},   // Maximum value
/* MEAN */    {{sCalc_Mean},       CALC_MEAN},  // Mean
/* MIN */     {{sCalc_Min},        CALC_MIN},   // Minimum value
/* MUL */     {{sCalc_Mul},        CALC_MUL},   // Multiplication
/* NEQ */     {{sCalc_Neq},        CALC_NEQ},   // Not equal
/* NOT */     {{sCalc_Not},        CALC_NOT},   // log. NOT
/* OR */      {{sCalc_Or},         CALC_OR},    // log. OR
/* PHD */     {{sCalc_Phd},        CALC_PHD},   // Percentage
/* POW */     {{sCalc_Pow},        CALC_POW},   // Exponentiation
/* ROUND */   {{sCalc_Round},      CALC_ROUND}, // Rounding
/* SIN */     {{sCalc_Sin},        CALC_SIN},   // Sine
/* SQRT */    {{sCalc_Sqrt},       CALC_SQRT},  // Square root
/* SUB */     {{sCalc_Sub},        CALC_MINUS}, // Subtraction
/* SUM */     {{sCalc_Sum},        CALC_SUM},   // Sum
/* TAN */     {{sCalc_Tan},        CALC_TAN},   // Tangent
/* XOR */     {{sCalc_Xor},        CALC_XOR}    // log. XOR
};

double const nRoundVal[] = {
    5.0e+0, 0.5e+0, 0.5e-1, 0.5e-2, 0.5e-3, 0.5e-4, 0.5e-5, 0.5e-6,
    0.5e-7, 0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,
    0.5e-15,0.5e-16
};

// First character may be any alphabetic or underscore.
const sal_Int32 coStartFlags =
        i18n::KParseTokens::ANY_LETTER_OR_NUMBER |
        i18n::KParseTokens::ASC_UNDERSCORE |
        i18n::KParseTokens::IGNORE_LEADING_WS;

// Continuing characters may be any alphanumeric, underscore, or dot.
const sal_Int32 coContFlags =
    ( coStartFlags | i18n::KParseTokens::ASC_DOT )
        & ~i18n::KParseTokens::IGNORE_LEADING_WS;

extern "C" {
static int SAL_CALL OperatorCompare( const void *pFirst, const void *pSecond)
{
    int nRet = 0;
    if( CALC_NAME == static_cast<const CalcOp*>(pFirst)->eOp )
    {
        if( CALC_NAME == static_cast<const CalcOp*>(pSecond)->eOp )
            nRet = static_cast<const CalcOp*>(pFirst)->pUName->compareTo(
                   *static_cast<const CalcOp*>(pSecond)->pUName );
        else
            nRet = static_cast<const CalcOp*>(pFirst)->pUName->compareToAscii(
                   static_cast<const CalcOp*>(pSecond)->pName );
    }
    else
    {
        if( CALC_NAME == static_cast<const CalcOp*>(pSecond)->eOp )
            nRet = -1 * static_cast<const CalcOp*>(pSecond)->pUName->compareToAscii(
                        static_cast<const CalcOp*>(pFirst)->pName );
        else
            nRet = strcmp( static_cast<const CalcOp*>(pFirst)->pName,
                           static_cast<const CalcOp*>(pSecond)->pName );
    }
    return nRet;
}
}// extern "C"

CalcOp* FindOperator( const OUString& rSrch )
{
    CalcOp aSrch;
    aSrch.pUName = &rSrch;
    aSrch.eOp = CALC_NAME;

    return static_cast<CalcOp*>(bsearch( static_cast<void*>(&aSrch),
                              static_cast<void const *>(aOpTable),
                              SAL_N_ELEMENTS( aOpTable ),
                              sizeof( CalcOp ),
                              OperatorCompare ));
}

SwHash* Find( const OUString& rStr, SwHash* const * ppTable,
              sal_uInt16 nTableSize, sal_uInt16* pPos )
{
    sal_uLong ii = 0;
    for( sal_Int32 n = 0; n < rStr.getLength(); ++n )
    {
        ii = ii << 1 ^ rStr[n];
    }
    ii %= nTableSize;

    if( pPos )
        *pPos = static_cast<sal_uInt16>(ii);

    for( SwHash* pEntry = *(ppTable+ii); pEntry; pEntry = pEntry->pNext.get() )
    {
        if( rStr == pEntry->aStr )
        {
            return pEntry;
        }
    }
    return nullptr;
}

inline LanguageType GetDocAppScriptLang( SwDoc const & rDoc )
{
    return static_cast<const SvxLanguageItem&>(rDoc.GetDefault(
               GetWhichOfScript( RES_CHRATR_LANGUAGE,
                                 SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ))
            )).GetLanguage();
}

static double lcl_ConvertToDateValue( SwDoc& rDoc, sal_Int32 nDate )
{
    double nRet = 0;
    SvNumberFormatter* pFormatter = rDoc.GetNumberFormatter();
    if( pFormatter )
    {
        const Date& rNull = pFormatter->GetNullDate();
        Date aDate( nDate >> 24, (nDate& 0x00FF0000) >> 16, nDate& 0x0000FFFF );
        nRet = aDate - rNull;
    }
    return nRet;
}

SwCalc::SwCalc( SwDoc& rD )
    : m_aErrExpr( OUString(), SwSbxValue(), nullptr )
    , m_nCommandPos(0)
    , m_rDoc( rD )
    , m_pLocaleDataWrapper( m_aSysLocale.GetLocaleDataPtr() )
    , m_pCharClass( &GetAppCharClass() )
    , m_nListPor( 0 )
    , m_eCurrOper( CALC_NAME )
    , m_eCurrListOper( CALC_NAME )
    , m_eError( SwCalcError::NONE )
{
    m_aErrExpr.aStr = "~C_ERR~";
    memset( m_aVarTable, 0, sizeof(m_aVarTable) );
    LanguageType eLang = GetDocAppScriptLang( m_rDoc );

    if( eLang != m_pLocaleDataWrapper->getLanguageTag().getLanguageType() ||
        eLang != m_pCharClass->getLanguageTag().getLanguageType() )
    {
        LanguageTag aLanguageTag( eLang );
        m_pCharClass = new CharClass( ::comphelper::getProcessComponentContext(), aLanguageTag );
        m_pLocaleDataWrapper = new LocaleDataWrapper( aLanguageTag );
    }

    m_sCurrSym = comphelper::string::strip(m_pLocaleDataWrapper->getCurrSymbol(), ' ');
    m_sCurrSym  = m_pCharClass->lowercase( m_sCurrSym );

    static sal_Char const
        sNType0[] = "false",
        sNType1[] = "true",
        sNType2[] = "pi",
        sNType3[] = "e",
        sNType4[] = "tables",
        sNType5[] = "graf",
        sNType6[] = "ole",
        sNType7[] = "page",
        sNType8[] = "para",
        sNType9[] = "word",
        sNType10[]= "char",

        sNType11[] = "user_firstname" ,
        sNType12[] = "user_lastname" ,
        sNType13[] = "user_initials" ,
        sNType14[] = "user_company" ,
        sNType15[] = "user_street" ,
        sNType16[] = "user_country" ,
        sNType17[] = "user_zipcode" ,
        sNType18[] = "user_city" ,
        sNType19[] = "user_title" ,
        sNType20[] = "user_position" ,
        sNType21[] = "user_tel_work" ,
        sNType22[] = "user_tel_home" ,
        sNType23[] = "user_fax" ,
        sNType24[] = "user_email" ,
        sNType25[] = "user_state" ,
        sNType26[] = "graph"
        ;
    static const sal_Char* const sNTypeTab[ 27 ] =
    {
        sNType0, sNType1, sNType2, sNType3, sNType4, sNType5,
        sNType6, sNType7, sNType8, sNType9, sNType10, sNType11,
        sNType12, sNType13, sNType14, sNType15, sNType16, sNType17,
        sNType18, sNType19, sNType20, sNType21, sNType22, sNType23,
        sNType24,

        // those have two HashIds
        sNType25, sNType26
    };
    static sal_uInt16 const aHashValue[ 27 ] =
    {
        34, 38, 43,  7, 18, 32, 22, 29, 30, 33,  3,
        28, 24, 40,  9, 11, 26, 45,  4, 23, 36, 44, 19,  5,  1,
        // those have two HashIds
        11, 38
    };
    static UserOptToken const aAdrToken[ 12 ] =
    {
        UserOptToken::Company, UserOptToken::Street, UserOptToken::Country, UserOptToken::Zip,
        UserOptToken::City, UserOptToken::Title, UserOptToken::Position, UserOptToken::TelephoneWork,
        UserOptToken::TelephoneHome, UserOptToken::Fax, UserOptToken::Email, UserOptToken::State
    };

    static sal_uInt16 SwDocStat::* const aDocStat1[ 3 ] =
    {
        &SwDocStat::nTable, &SwDocStat::nGrf, &SwDocStat::nOLE
    };
    static sal_uLong SwDocStat::* const aDocStat2[ 4 ] =
    {
        &SwDocStat::nPage, &SwDocStat::nPara,
        &SwDocStat::nWord, &SwDocStat::nChar
    };

#if TBLSZ != 47
#error Did you adjust all hash values?
#endif

    const SwDocStat& rDocStat = m_rDoc.getIDocumentStatistics().GetDocStat();

    SwSbxValue nVal;
    OUString sTmpStr;
    sal_uInt16 n;

    for( n = 0; n < 25; ++n )
    {
        sTmpStr = OUString::createFromAscii(sNTypeTab[n]);
        m_aVarTable[ aHashValue[ n ] ] = new SwCalcExp( sTmpStr, nVal, nullptr );
    }

    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 0 ] ])->nValue.PutBool( false );
    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 1 ] ])->nValue.PutBool( true );
    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 2 ] ])->nValue.PutDouble( F_PI );
    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 3 ] ])->nValue.PutDouble( 2.7182818284590452354 );

    for( n = 0; n < 3; ++n )
        static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ n + 4 ] ])->nValue.PutLong( rDocStat.*aDocStat1[ n ]  );
    for( n = 0; n < 4; ++n )
        static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ n + 7 ] ])->nValue.PutLong( rDocStat.*aDocStat2[ n ]  );

    SvtUserOptions& rUserOptions = SW_MOD()->GetUserOptions();

    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 11 ] ])->nValue.PutString( rUserOptions.GetFirstName() );
    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 12 ] ])->nValue.PutString( rUserOptions.GetLastName() );
    static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ 13 ] ])->nValue.PutString( rUserOptions.GetID() );

    for( n = 0; n < 11; ++n )
        static_cast<SwCalcExp*>(m_aVarTable[ aHashValue[ n + 14 ] ])->nValue.PutString(
                                        rUserOptions.GetToken( aAdrToken[ n ] ));

    nVal.PutString( rUserOptions.GetToken( aAdrToken[ 11 ] ));
    sTmpStr = OUString::createFromAscii(sNTypeTab[25]);
    m_aVarTable[ aHashValue[ 25 ] ]->pNext.reset( new SwCalcExp( sTmpStr, nVal, nullptr ) );

} // SwCalc::SwCalc

SwCalc::~SwCalc()
{
    for(SwHash* p : m_aVarTable)
        delete p;

    if( m_pLocaleDataWrapper != m_aSysLocale.GetLocaleDataPtr() )
        delete m_pLocaleDataWrapper;
    if( m_pCharClass != &GetAppCharClass() )
        delete m_pCharClass;
}

SwSbxValue SwCalc::Calculate( const OUString& rStr )
{
    m_eError = SwCalcError::NONE;
    SwSbxValue nResult;

    if( rStr.isEmpty() )
        return nResult;

    m_nListPor = 0;
    m_eCurrListOper = CALC_PLUS; // default: sum

    m_sCommand = rStr;
    m_nCommandPos = 0;

    while( (m_eCurrOper = GetToken()) != CALC_ENDCALC && m_eError == SwCalcError::NONE )
        nResult = Expr();

    if( m_eError != SwCalcError::NONE)
        nResult.PutDouble( DBL_MAX );

    return nResult;
}

OUString SwCalc::GetStrResult( const SwSbxValue& rVal )
{
    if( !rVal.IsDouble() )
    {
        return rVal.GetOUString();
    }
    return GetStrResult( rVal.GetDouble() );
}

OUString SwCalc::GetStrResult( double nValue )
{
    if( nValue >= DBL_MAX )
        switch( m_eError )
        {
        case SwCalcError::Syntax          :   return RESOURCE->aCalc_Syntax;
        case SwCalcError::DivByZero       :   return RESOURCE->aCalc_ZeroDiv;
        case SwCalcError::FaultyBrackets  :   return RESOURCE->aCalc_Brack;
        case SwCalcError::OverflowInPower :   return RESOURCE->aCalc_Pow;
        case SwCalcError::Overflow        :   return RESOURCE->aCalc_Overflow;
        default                           :   return RESOURCE->aCalc_Default;
        }

    const sal_Int32 nDecPlaces = 15;
    OUString aRetStr( ::rtl::math::doubleToUString(
                        nValue,
                        rtl_math_StringFormat_Automatic,
                        nDecPlaces,
                        m_pLocaleDataWrapper->getNumDecimalSep()[0],
                        true ));
    return aRetStr;
}

SwCalcExp* SwCalc::VarInsert( const OUString &rStr )
{
    OUString aStr = m_pCharClass->lowercase( rStr );
    return VarLook( aStr, true );
}

SwCalcExp* SwCalc::VarLook( const OUString& rStr, bool bIns )
{
    m_aErrExpr.nValue.SetVoidValue(false);

    sal_uInt16 ii = 0;
    OUString aStr = m_pCharClass->lowercase( rStr );

    SwHash* pFnd = Find( aStr, m_aVarTable, TBLSZ, &ii );

    if( !pFnd )
    {
        // then check doc
        SwHash* const * ppDocTable = m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().GetFieldTypeTable();
        for( SwHash* pEntry = *(ppDocTable+ii); pEntry; pEntry = pEntry->pNext.get() )
        {
            if( aStr == pEntry->aStr )
            {
                // then insert here
                pFnd = new SwCalcExp( aStr, SwSbxValue(),
                                    static_cast<SwCalcFieldType*>(pEntry)->pFieldType );
                pFnd->pNext.reset( *(m_aVarTable+ii) );
                *(m_aVarTable+ii) = pFnd;
                break;
            }
        }
    }

    if( pFnd )
    {
        SwCalcExp* pFndExp = static_cast<SwCalcExp*>(pFnd);

        if( pFndExp->pFieldType && pFndExp->pFieldType->Which() == SwFieldIds::User )
        {
            SwUserFieldType* pUField = const_cast<SwUserFieldType*>(static_cast<const SwUserFieldType*>(pFndExp->pFieldType));
            if( nsSwGetSetExpType::GSE_STRING & pUField->GetType() )
            {
                pFndExp->nValue.PutString( pUField->GetContent() );
            }
            else if( !pUField->IsValid() )
            {
                // Save the current values...
                sal_uInt16 nListPor = m_nListPor;
                SwSbxValue nLastLeft = m_nLastLeft;
                SwSbxValue nNumberValue = m_nNumberValue;
                sal_Int32 nCommandPos = m_nCommandPos;
                SwCalcOper eCurrOper = m_eCurrOper;
                SwCalcOper eCurrListOper = m_eCurrListOper;

                pFndExp->nValue.PutDouble( pUField->GetValue( *this ) );

                // ...and write them back.
                m_nListPor = nListPor;
                m_nLastLeft = nLastLeft;
                m_nNumberValue = nNumberValue;
                m_nCommandPos = nCommandPos;
                m_eCurrOper = eCurrOper;
                m_eCurrListOper = eCurrListOper;
            }
            else
            {
                pFndExp->nValue.PutDouble( pUField->GetValue() );
            }
        }
        else if ( !pFndExp->pFieldType && pFndExp->nValue.IsDBvalue() )
        {
            if ( pFndExp->nValue.IsString() )
                m_aErrExpr.nValue.PutString( pFndExp->nValue.GetOUString() );
            else if ( pFndExp->nValue.IsDouble() )
                m_aErrExpr.nValue.PutDouble( pFndExp->nValue.GetDouble() );
            pFndExp = &m_aErrExpr;
        }
        return pFndExp;
    }

    // At this point the "real" case variable has to be used
    OUString const sTmpName( ::ReplacePoint(rStr) );

    if( !bIns )
    {
#if HAVE_FEATURE_DBCONNECTIVITY
        SwDBManager *pMgr = m_rDoc.GetDBManager();

        OUString sDBName(GetDBName( sTmpName ));
        OUString sSourceName(sDBName.getToken(0, DB_DELIM));
        OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM));
        if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() &&
            pMgr->OpenDataSource(sSourceName, sTableName))
        {
            OUString sColumnName( GetColumnName( sTmpName ));
            OSL_ENSURE(!sColumnName.isEmpty(), "Missing DB column name");

            OUString sDBNum( SwFieldType::GetTypeStr(TYP_DBSETNUMBERFLD) );
            sDBNum = m_pCharClass->lowercase(sDBNum);

            // Initialize again because this doesn't happen in docfld anymore for
            // elements != SwFieldIds::Database. E.g. if there is an expression field before
            // an DB_Field in a document.
            VarChange( sDBNum, pMgr->GetSelectedRecordId(sSourceName, sTableName));

            if( sDBNum.equalsIgnoreAsciiCase(sColumnName) )
            {
                m_aErrExpr.nValue.PutLong(long(pMgr->GetSelectedRecordId(sSourceName, sTableName)));
                return &m_aErrExpr;
            }

            sal_uLong nTmpRec = 0;
            if( nullptr != ( pFnd = Find( sDBNum, m_aVarTable, TBLSZ ) ) )
                nTmpRec = static_cast<SwCalcExp*>(pFnd)->nValue.GetULong();

            OUString sResult;
            double nNumber = DBL_MAX;

            LanguageType nLang = m_pLocaleDataWrapper->getLanguageTag().getLanguageType();
            if(pMgr->GetColumnCnt( sSourceName, sTableName, sColumnName,
                                    nTmpRec, nLang, sResult, &nNumber ))
            {
                if (nNumber != DBL_MAX)
                    m_aErrExpr.nValue.PutDouble( nNumber );
                else
                    m_aErrExpr.nValue.PutString( sResult );

                return &m_aErrExpr;
            }
        }
        else
#endif
        {
            //data source was not available - set return to "NoValue"
            m_aErrExpr.nValue.SetVoidValue(true);
        }
        // NEVER save!
        return &m_aErrExpr;
    }

    SwCalcExp* pNewExp = new SwCalcExp( aStr, SwSbxValue(), nullptr );
    pNewExp->pNext.reset( m_aVarTable[ ii ] );
    m_aVarTable[ ii ] = pNewExp;

    OUString sColumnName( GetColumnName( sTmpName ));
    OSL_ENSURE( !sColumnName.isEmpty(), "Missing DB column name" );
    if( sColumnName.equalsIgnoreAsciiCase(
                            SwFieldType::GetTypeStr( TYP_DBSETNUMBERFLD ) ))
    {
#if HAVE_FEATURE_DBCONNECTIVITY
        SwDBManager *pMgr = m_rDoc.GetDBManager();
        OUString sDBName(GetDBName( sTmpName ));
        OUString sSourceName(sDBName.getToken(0, DB_DELIM));
        OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM));
        if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() &&
            pMgr->OpenDataSource(sSourceName, sTableName) &&
            !pMgr->IsInMerge())
        {
            pNewExp->nValue.PutULong( pMgr->GetSelectedRecordId(sSourceName, sTableName));
        }
        else
#endif
        {
            pNewExp->nValue.SetVoidValue(true);
        }
    }

    return pNewExp;
}

void SwCalc::VarChange( const OUString& rStr, double nValue )
{
    SwSbxValue aVal( nValue );
    VarChange( rStr, aVal );
}

void SwCalc::VarChange( const OUString& rStr, const SwSbxValue& rValue )
{
    OUString aStr = m_pCharClass->lowercase( rStr );

    sal_uInt16 nPos = 0;
    SwCalcExp* pFnd = static_cast<SwCalcExp*>(Find( aStr, m_aVarTable, TBLSZ, &nPos ));

    if( !pFnd )
    {
        pFnd = new SwCalcExp( aStr, SwSbxValue( rValue ), nullptr );
        pFnd->pNext.reset( m_aVarTable[ nPos ] );
        m_aVarTable[ nPos ] = pFnd;
    }
    else
    {
        pFnd->nValue = rValue;
    }
}

bool SwCalc::Push( const SwUserFieldType* pUserFieldType )
{
    if( m_aRekurStack.end() != std::find(m_aRekurStack.begin(), m_aRekurStack.end(), pUserFieldType ) )
        return false;

    m_aRekurStack.push_back( pUserFieldType );
    return true;
}

void SwCalc::Pop()
{
    OSL_ENSURE( m_aRekurStack.size(), "SwCalc: Pop on an invalid pointer" );

    m_aRekurStack.pop_back();
}

SwCalcOper SwCalc::GetToken()
{
    if( m_nCommandPos >= m_sCommand.getLength() )
        return m_eCurrOper = CALC_ENDCALC;

    using namespace ::com::sun::star::i18n;
    {
        // Parse any token.
        ParseResult aRes = m_pCharClass->parseAnyToken( m_sCommand, m_nCommandPos,
                                                      coStartFlags, OUString(),
                                                      coContFlags, OUString());

        bool bSetError = true;
        sal_Int32 nRealStt = m_nCommandPos + aRes.LeadingWhiteSpace;
        if( aRes.TokenType & (KParseType::ASC_NUMBER | KParseType::UNI_NUMBER) )
        {
            m_nNumberValue.PutDouble( aRes.Value );
            m_eCurrOper = CALC_NUMBER;
            bSetError = false;
        }
        else if( aRes.TokenType & KParseType::IDENTNAME )
        {
            OUString aName( m_sCommand.copy( nRealStt,
                            aRes.EndPos - nRealStt ) );
            //#101436#: The variable may contain a database name. It must not be
            // converted to lower case! Instead all further comparisons must be
            // done case-insensitive
            OUString sLowerCaseName = m_pCharClass->lowercase( aName );
            // catch currency symbol
            if( sLowerCaseName == m_sCurrSym )
            {
                m_nCommandPos = aRes.EndPos;
                return GetToken(); // call again
            }

            // catch operators
            CalcOp* pFnd = ::FindOperator( sLowerCaseName );
            if( pFnd )
            {
                switch( ( m_eCurrOper = pFnd->eOp ) )
                {
                case CALC_SUM:
                case CALC_MEAN:
                    m_eCurrListOper = CALC_PLUS;
                    break;
                case CALC_MIN:
                    m_eCurrListOper = CALC_MIN_IN;
                    break;
                case CALC_MAX:
                    m_eCurrListOper = CALC_MAX_IN;
                    break;
                case CALC_DATE:
                    m_eCurrListOper = CALC_MONTH;
                    break;
                default:
                    break;
                }
                m_nCommandPos = aRes.EndPos;
                return m_eCurrOper;
            }
            m_aVarName = aName;
            m_eCurrOper = CALC_NAME;
            bSetError = false;
        }
        else if ( aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING )
        {
            m_nNumberValue.PutString( aRes.DequotedNameOrString );
            m_eCurrOper = CALC_NUMBER;
            bSetError = false;
        }
        else if( aRes.TokenType & KParseType::ONE_SINGLE_CHAR )
        {
            OUString aName( m_sCommand.copy( nRealStt,
                              aRes.EndPos - nRealStt ));
            if( 1 == aName.getLength() )
            {
                bSetError = false;
                sal_Unicode ch = aName[0];
                switch( ch )
                {
                case ';':
                    if( CALC_MONTH == m_eCurrListOper || CALC_DAY == m_eCurrListOper )
                    {
                        m_eCurrOper = m_eCurrListOper;
                        break;
                    }
                    SAL_FALLTHROUGH;
                case '\n':
                    m_eCurrOper = CALC_PRINT;
                    break;

                case '%':
                case '^':
                case '*':
                case '/':
                case '+':
                case '-':
                case '(':
                case ')':
                    m_eCurrOper = SwCalcOper(ch);
                    break;

                case '=':
                case '!':
                    {
                        SwCalcOper eTmp2;
                        if( '=' == ch )
                        {
                            m_eCurrOper = SwCalcOper('=');
                            eTmp2 = CALC_EQ;
                        }
                        else
                        {
                            m_eCurrOper = CALC_NOT;
                            eTmp2 = CALC_NEQ;
                        }

                        if( aRes.EndPos < m_sCommand.getLength() &&
                            '=' == m_sCommand[aRes.EndPos] )
                        {
                            m_eCurrOper = eTmp2;
                            ++aRes.EndPos;
                        }
                    }
                    break;

                case cListDelim:
                    m_eCurrOper = m_eCurrListOper;
                    break;

                case '[':
                    if( aRes.EndPos < m_sCommand.getLength() )
                    {
                        m_aVarName.clear();
                        sal_Int32 nFndPos = aRes.EndPos,
                                  nSttPos = nFndPos;

                        do {
                            if( -1 != ( nFndPos =
                                m_sCommand.indexOf( ']', nFndPos )) )
                            {
                                // ignore the ]
                                if ('\\' == m_sCommand[nFndPos-1])
                                {
                                    m_aVarName += m_sCommand.copy( nSttPos,
                                                    nFndPos - nSttPos - 1 );
                                    nSttPos = ++nFndPos;
                                }
                                else
                                    break;
                            }
                        } while( nFndPos != -1 );

                        if( nFndPos != -1 )
                        {
                            if( nSttPos != nFndPos )
                                m_aVarName += m_sCommand.copy( nSttPos,
                                                    nFndPos - nSttPos );
                            aRes.EndPos = nFndPos + 1;
                            m_eCurrOper = CALC_NAME;
                        }
                        else
                            bSetError = true;
                    }
                    else
                    {
                        bSetError = true;
                    }
                    break;

                default:
                    bSetError = true;
                    break;
                }
            }
        }
        else if( aRes.TokenType & KParseType::BOOLEAN )
        {
            OUString aName( m_sCommand.copy( nRealStt,
                                         aRes.EndPos - nRealStt ));
            if( !aName.isEmpty() )
            {
                sal_Unicode ch = aName[0];

                bSetError = true;
                if ('<' == ch || '>' == ch)
                {
                    bSetError = false;

                    SwCalcOper eTmp2 = ('<' == ch) ? CALC_LEQ : CALC_GEQ;
                    m_eCurrOper = ('<' == ch) ? CALC_LES : CALC_GRE;

                    if( 2 == aName.getLength() && '=' == aName[1] )
                        m_eCurrOper = eTmp2;
                    else if( 1 != aName.getLength() )
                        bSetError = true;
                }
            }
        }
        else if( nRealStt == m_sCommand.getLength() )
        {
            m_eCurrOper = CALC_ENDCALC;
            bSetError = false;
        }

        if( bSetError )
        {
            m_eError = SwCalcError::Syntax;
            m_eCurrOper = CALC_PRINT;
        }
        m_nCommandPos = aRes.EndPos;
    };

    return m_eCurrOper;
}

SwSbxValue SwCalc::Term()
{
    SwSbxValue left( Prim() );
    m_nLastLeft = left;
    for(;;)
    {
        sal_uInt16 nSbxOper = USHRT_MAX;

        switch( m_eCurrOper )
        {
        case CALC_AND:
            {
                GetToken();
                bool bB = Prim().GetBool();
                left.PutBool( left.GetBool() && bB );
            }
            break;
        case CALC_OR:
            {
                GetToken();
                bool bB = Prim().GetBool();
                left.PutBool( left.GetBool() || bB );
            }
            break;
        case CALC_XOR:
            {
                GetToken();
                bool bR = Prim().GetBool();
                bool bL = left.GetBool();
                left.PutBool( (bL && !bR) || (!bL && bR) );
            }
            break;

        case CALC_EQ:   nSbxOper = SbxEQ;   break;
        case CALC_NEQ:  nSbxOper = SbxNE;   break;
        case CALC_LEQ:  nSbxOper = SbxLE;   break;
        case CALC_GEQ:  nSbxOper = SbxGE;   break;
        case CALC_GRE:  nSbxOper = SbxGT;   break;
        case CALC_LES:  nSbxOper = SbxLT;   break;

        case CALC_MUL:  nSbxOper = SbxMUL;  break;
        case CALC_DIV:  nSbxOper = SbxDIV;  break;

        case CALC_MIN_IN:
            {
                GetToken();
                SwSbxValue e = Prim();
                left = left.GetDouble() < e.GetDouble() ? left : e;
            }
            break;
        case CALC_MAX_IN:
            {
                GetToken();
                SwSbxValue e = Prim();
                left = left.GetDouble() > e.GetDouble() ? left : e;
            }
            break;
        case CALC_MONTH:
            {
                GetToken();
                SwSbxValue e = Prim();
                sal_Int32 nYear = (sal_Int32) floor( left.GetDouble() );
                nYear = nYear & 0x0000FFFF;
                sal_Int32 nMonth = (sal_Int32) floor( e.GetDouble() );
                nMonth = ( nMonth & 0x000000FF ) << 16;
                left.PutLong( nMonth + nYear );
                m_eCurrOper = CALC_DAY;
            }
            break;
        case CALC_DAY:
            {
                GetToken();
                SwSbxValue e = Prim();
                sal_Int32 nYearMonth = (sal_Int32) floor( left.GetDouble() );
                nYearMonth = nYearMonth & 0x00FFFFFF;
                sal_Int32 nDay = (sal_Int32) floor( e.GetDouble() );
                nDay = ( nDay & 0x000000FF ) << 24;
                left = lcl_ConvertToDateValue( m_rDoc, nDay + nYearMonth );
            }
            break;
        case CALC_ROUND:
            {
                GetToken();
                SwSbxValue e = Prim();

                double fVal = 0;
                double fFac = 1;
                sal_Int32 nDec = (sal_Int32) floor( e.GetDouble() );
                if( nDec < -20 || nDec > 20 )
                {
                    m_eError = SwCalcError::Overflow;
                    left.Clear();
                    return left;
                }
                fVal = left.GetDouble();
                if( nDec >= 0)
                {
                    for (sal_Int32 i = 0; i < nDec; ++i )
                        fFac *= 10.0;
                }
                else
                {
                    for (sal_Int32 i = 0; i < -nDec; ++i )
                        fFac /= 10.0;
                }

                fVal *= fFac;
                bool bSign;
                if (fVal < 0.0)
                {
                    fVal *= -1.0;
                    bSign = true;
                }
                else
                {
                    bSign = false;
                }

                // rounding
                double fNum = fVal; // find the exponent
                int nExp = 0;
                if( fNum > 0 )
                {
                    while( fNum < 1.0 )
                    {
                        fNum *= 10.0;
                        --nExp;
                    }
                    while( fNum >= 10.0 )
                    {
                        fNum /= 10.0;
                        ++nExp;
                    }
                }
                nExp = 15 - nExp;
                if( nExp > 15 )
                    nExp = 15;
                else if( nExp <= 1 )
                    nExp = 0;
                fVal = floor( fVal+ 0.5 + nRoundVal[ nExp ] );

                if (bSign)
                    fVal *= -1.0;

                fVal /= fFac;

                left.PutDouble( fVal );
            }
            break;

//#77448# (=2*3^2 != 18)

        default:
            return left;
        }

        if( USHRT_MAX != nSbxOper )
        {
            // #i47706: cast to SbxOperator AFTER comparing to USHRT_MAX
            SbxOperator eSbxOper = (SbxOperator)nSbxOper;

            GetToken();
            if( SbxEQ <= eSbxOper && eSbxOper <= SbxGE )
            {
                left.PutBool( left.Compare( eSbxOper, Prim() ));
            }
            else
            {
                SwSbxValue aRight( Prim() );
                aRight.MakeDouble();
                left.MakeDouble();

                if( SbxDIV == eSbxOper && !aRight.GetDouble() )
                    m_eError = SwCalcError::DivByZero;
                else
                    left.Compute( eSbxOper, aRight );
            }
        }
    }
}

SwSbxValue SwCalc::StdFunc(pfCalc pFnc, bool bChkTrig)
{
    SwSbxValue nErg;
    GetToken();
    double nVal = Prim().GetDouble();
    if( !bChkTrig || ( nVal > -1 && nVal < 1 ) )
        nErg.PutDouble( (*pFnc)( nVal ) );
    else
        m_eError = SwCalcError::Overflow;
    return nErg;
}

SwSbxValue SwCalc::PrimFunc(bool &rChkPow)
{
    rChkPow = false;

    switch (m_eCurrOper)
    {
        case CALC_SIN:
            return StdFunc(&sin, false);
            break;
        case CALC_COS:
            return StdFunc(&cos, false);
            break;
        case CALC_TAN:
            return StdFunc(&tan, false);
            break;
        case CALC_ATAN:
            return StdFunc(&atan, false);
            break;
        case CALC_ASIN:
            return StdFunc(&asin, true);
            break;
        case CALC_ACOS:
            return StdFunc(&acos, true);
            break;
        case CALC_NOT:
        {
            GetToken();
            SwSbxValue nErg = Prim();
            if( SbxSTRING == nErg.GetType() )
            {
                nErg.PutBool( nErg.GetOUString().isEmpty() );
            }
            else if(SbxBOOL == nErg.GetType() )
            {
                nErg.PutBool(!nErg.GetBool());
            }
            // Evaluate arguments manually so that the binary NOT below does not
            // get called. We want a BOOLEAN NOT.
            else if (nErg.IsNumeric())
            {
                nErg.PutLong( nErg.GetDouble() == 0.0 ? 1 : 0 );
            }
            else
            {
                OSL_FAIL( "unexpected case. computing binary NOT" );
                //!! computes a binary NOT
                nErg.Compute( SbxNOT, nErg );
            }
            return nErg;
            break;
        }
        case CALC_NUMBER:
        {
            SwSbxValue nErg;
            if( GetToken() == CALC_PHD )
            {
                double aTmp = m_nNumberValue.GetDouble();
                aTmp *= 0.01;
                nErg.PutDouble( aTmp );
                GetToken();
            }
            else if( m_eCurrOper == CALC_NAME )
            {
                m_eError = SwCalcError::Syntax;
            }
            else
            {
                nErg = m_nNumberValue;
                rChkPow = true;
            }
            return nErg;
            break;
        }
        case CALC_NAME:
        {
            SwSbxValue nErg;
            switch(SwCalcOper eOper = GetToken())
            {
                case CALC_ASSIGN:
                {
                    SwCalcExp* n = VarInsert(m_aVarName);
                    GetToken();
                    nErg = n->nValue = Expr();
                    break;
                }
                default:
                    nErg = VarLook(m_aVarName)->nValue;
                    // Explicitly disallow unknown function names (followed by "("),
                    // allow unknown variable names (equal to zero)
                    if (nErg.IsVoidValue() && (eOper == CALC_LP))
                        m_eError = SwCalcError::Syntax;
                    else
                        rChkPow = true;
                    break;
            }
            return nErg;
            break;
        }
        case CALC_MINUS:
        {
            SwSbxValue nErg;
            GetToken();
            nErg.PutDouble( -(Prim().GetDouble()) );
            return nErg;
            break;
        }
        case CALC_LP:
        {
            GetToken();
            SwSbxValue nErg = Expr();
            if( m_eCurrOper != CALC_RP )
            {
                m_eError = SwCalcError::FaultyBrackets;
            }
            else
            {
                GetToken();
                rChkPow = true; // in order for =(7)^2 to work
            }
            return nErg;
            break;
        }
        case CALC_MEAN:
        {
            m_nListPor = 1;
            GetToken();
            SwSbxValue nErg = Expr();
            double aTmp = nErg.GetDouble();
            aTmp /= m_nListPor;
            nErg.PutDouble( aTmp );
            return nErg;
            break;
        }
        case CALC_SQRT:
        {
            GetToken();
            SwSbxValue nErg = Prim();
            if( nErg.GetDouble() < 0 )
                m_eError = SwCalcError::Overflow;
            else
                nErg.PutDouble( sqrt( nErg.GetDouble() ));
            return nErg;
            break;
        }
        case CALC_SUM:
        case CALC_DATE:
        case CALC_MIN:
        case CALC_MAX:
        {
            GetToken();
            SwSbxValue nErg = Expr();
            return nErg;
            break;
        }
        case CALC_ENDCALC:
        {
            SwSbxValue nErg;
            nErg.Clear();
            return nErg;
            break;
        }
        default:
            m_eError = SwCalcError::Syntax;
            break;
    }

    return SwSbxValue();
}

SwSbxValue SwCalc::Prim()
{
    bool bChkPow;
    SwSbxValue nErg = PrimFunc(bChkPow);

    if (bChkPow && m_eCurrOper == CALC_POW)
    {
        double dleft = nErg.GetDouble();
        GetToken();
        double right = Prim().GetDouble();

        double fraction, integer;
        fraction = modf( right, &integer );
        if( ( dleft < 0.0 && 0.0 != fraction ) ||
            ( 0.0 == dleft && right < 0.0 ) )
        {
            m_eError = SwCalcError::Overflow;
            nErg.Clear();
        }
        else
        {
            dleft = pow(dleft, right );
            if( dleft == HUGE_VAL )
            {
                m_eError = SwCalcError::OverflowInPower;
                nErg.Clear();
            }
            else
            {
                nErg.PutDouble( dleft );
            }
        }
    }

    return nErg;
}

SwSbxValue  SwCalc::Expr()
{
    SwSbxValue left = Term();
    m_nLastLeft = left;
    for(;;)
    {
        switch(m_eCurrOper)
        {
            case CALC_PLUS:
            {
                GetToken();
                left.MakeDouble();
                SwSbxValue right(Term());
                right.MakeDouble();
                left.Compute(SbxPLUS, right);
                m_nListPor++;
                break;
            }
            case CALC_MINUS:
            {
                GetToken();
                left.MakeDouble();
                SwSbxValue right(Term());
                right.MakeDouble();
                left.Compute(SbxMINUS, right);
                break;
            }
            default:
            {
                return left;
            }
        }
    }
}

OUString SwCalc::GetColumnName(const OUString& rName)
{
    sal_Int32 nPos = rName.indexOf(DB_DELIM);
    if( -1 != nPos )
    {
        nPos = rName.indexOf(DB_DELIM, nPos + 1);

        if( -1 != nPos )
            return rName.copy(nPos + 1);
    }
    return rName;
}

OUString SwCalc::GetDBName(const OUString& rName)
{
    sal_Int32 nPos = rName.indexOf(DB_DELIM);
    if( -1 != nPos )
    {
        nPos = rName.indexOf(DB_DELIM, nPos + 1);

        if( -1 != nPos )
            return rName.copy( 0, nPos );
    }
    SwDBData aData = m_rDoc.GetDBData();
    return aData.sDataSource + OUStringLiteral1(DB_DELIM) + aData.sCommand;
}

namespace
{
    bool lcl_Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                                double& rVal,
                                const LocaleDataWrapper* const pLclData )
    {
        assert(pLclData);
        const sal_Unicode nCurrCmdPos = rCommandPos;
        rtl_math_ConversionStatus eStatus;
        const sal_Unicode* pEnd;
        rVal = pLclData->stringToDouble( rCommand.getStr() + rCommandPos,
                                         rCommand.getStr() + rCommand.getLength(),
                                         true,
                                         &eStatus,
                                         &pEnd );
        rCommandPos = static_cast<sal_Int32>(pEnd - rCommand.getStr());

        return rtl_math_ConversionStatus_Ok == eStatus &&
               nCurrCmdPos != rCommandPos;
    }
}

bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                         double& rVal )
{
    const SvtSysLocale aSysLocale;
    return lcl_Str2Double( rCommand, rCommandPos, rVal, aSysLocale.GetLocaleDataPtr() );
}

bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                         double& rVal, SwDoc const * const pDoc )
{
    const SvtSysLocale aSysLocale;
    std::unique_ptr<const LocaleDataWrapper> pLclD;
    if( pDoc )
    {
        LanguageType eLang = GetDocAppScriptLang( *pDoc );
        if (eLang != aSysLocale.GetLanguageTag().getLanguageType())
        {
            pLclD.reset( new LocaleDataWrapper( LanguageTag( eLang )) );
        }
    }

    bool const bRet = lcl_Str2Double( rCommand, rCommandPos, rVal,
            (pLclD.get()) ? pLclD.get() : aSysLocale.GetLocaleDataPtr() );

    return bRet;
}

bool SwCalc::IsValidVarName( const OUString& rStr, OUString* pValidName )
{
    bool bRet = false;
    using namespace ::com::sun::star::i18n;
    {
        // Parse any token.
        ParseResult aRes = GetAppCharClass().parseAnyToken( rStr, 0,
                                                    coStartFlags, OUString(),
                                                     coContFlags, OUString() );

        if( aRes.TokenType & KParseType::IDENTNAME )
        {
            bRet = aRes.EndPos == rStr.getLength();
            if( pValidName )
            {
                *pValidName = rStr.copy( aRes.LeadingWhiteSpace,
                                         aRes.EndPos - aRes.LeadingWhiteSpace );
            }
        }
        else if( pValidName )
            pValidName->clear();
    }
    return bRet;
}

SwHash::SwHash(const OUString& rStr)
    : aStr(rStr)
    , pNext(nullptr)
{
}

SwHash::~SwHash()
{
}

void DeleteHashTable( SwHash **ppHashTable, sal_uInt16 nCount )
{
    for ( sal_uInt16 i = 0; i < nCount; ++i )
        delete *(ppHashTable+i);
    delete [] ppHashTable;
}

SwCalcExp::SwCalcExp(const OUString& rStr, const SwSbxValue& rVal,
                      const SwFieldType* pType)
    : SwHash(rStr)
    , nValue(rVal)
    , pFieldType(pType)
{
}

bool SwSbxValue::GetBool() const
{
    return SbxSTRING == GetType() ? !GetOUString().isEmpty()
                                  : SbxValue::GetBool();
}

double SwSbxValue::GetDouble() const
{
    double nRet;
    if( SbxSTRING == GetType() )
    {
        sal_Int32 nStt = 0;
        SwCalc::Str2Double( GetOUString(), nStt, nRet );
    }
    else if (IsBool())
    {
        nRet = GetBool() ? 1.0 : 0.0;
    }
    else
    {
        nRet = SbxValue::GetDouble();
    }
    return nRet;
}

SwSbxValue& SwSbxValue::MakeDouble()
{
    if( GetType() == SbxSTRING  || GetType() == SbxBOOL )
        PutDouble( GetDouble() );
    return *this;
}

#ifdef STANDALONE_HASHCALC

// this is example code how to create hash values in the CTOR:

#include <stdio.h>
void main()
{
    static sal_Char
        sNType0[] = "false",    sNType1[] = "true",     sNType2[] = "pi",
        sNType3[] = "e",        sNType4[] = "tables",   sNType5[] = "graf",
        sNType6[] = "ole",      sNType7[] = "page",     sNType8[] = "para",
        sNType9[] = "word",     sNType10[]= "char",
        sNType11[] = "user_company" ,       sNType12[] = "user_firstname" ,
        sNType13[] = "user_lastname" ,      sNType14[] = "user_initials",
        sNType15[] = "user_street" ,        sNType16[] = "user_country" ,
        sNType17[] = "user_zipcode" ,       sNType18[] = "user_city" ,
        sNType19[] = "user_title" ,         sNType20[] = "user_position" ,
        sNType21[] = "user_tel_home",       sNType22[] = "user_tel_work",
        sNType23[] = "user_fax" ,           sNType24[] = "user_email" ,
        sNType25[] = "user_state",          sNType26[] = "graph"
        ;

    static const sal_Char* sNTypeTab[ 27 ] =
    {
        sNType0, sNType1, sNType2, sNType3, sNType4, sNType5,
        sNType6, sNType7, sNType8, sNType9, sNType10, sNType11,
        sNType12, sNType13, sNType14, sNType15, sNType16, sNType17,
        sNType18, sNType19, sNType20, sNType21, sNType22, sNType23,
        sNType24, sNType25, sNType26
    };

    const unsigned short nTableSize = 47;
    int aArr[ nTableSize ] = { 0 };
    sal_Char ch;

    for( int n = 0; n < 27; ++n )
    {
        unsigned int ii = 0;
        const sal_Char* pp = sNTypeTab[ n ];

        while( *pp )
        {
            ii = ii << 1 ^ *pp++;
        }
        ii %= nTableSize;

        ch = aArr[ ii ] ? 'X' : ' ';
        aArr[ ii ] = 1;
        printf( "%-20s -> %3u [%c]\n", sNTypeTab[ n ], ii, ch );
    }
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
