/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#include "oox/xls/addressconverter.hxx"

#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/sheet/XCellRangeAddressable.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <osl/diagnose.h>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include "oox/core/filterbase.hxx"
#include "oox/xls/biffinputstream.hxx"
#include "oox/xls/biffoutputstream.hxx"

namespace oox {
namespace xls {

// ============================================================================

using namespace ::com::sun::star::container;
using namespace ::com::sun::star::sheet;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::uno;

using ::rtl::OStringBuffer;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;
using ::rtl::OUStringToOString;

// ============================================================================

namespace {

//! TODO: this limit may change, is there a way to obtain it via API?
const sal_Int16 API_MAXTAB          = 255;

const sal_Int32 OOX_MAXCOL          = static_cast< sal_Int32 >( (1 << 14) - 1 );
const sal_Int32 OOX_MAXROW          = static_cast< sal_Int32 >( (1 << 20) - 1 );
const sal_Int16 OOX_MAXTAB          = static_cast< sal_Int16 >( (1 << 15) - 1 );

const sal_Int32 BIFF2_MAXCOL        = 255;
const sal_Int32 BIFF2_MAXROW        = 16383;
const sal_Int16 BIFF2_MAXTAB        = 0;

const sal_Int32 BIFF3_MAXCOL        = BIFF2_MAXCOL;
const sal_Int32 BIFF3_MAXROW        = BIFF2_MAXROW;
const sal_Int16 BIFF3_MAXTAB        = BIFF2_MAXTAB;

const sal_Int32 BIFF4_MAXCOL        = BIFF3_MAXCOL;
const sal_Int32 BIFF4_MAXROW        = BIFF3_MAXROW;
const sal_Int16 BIFF4_MAXTAB        = 32767;

const sal_Int32 BIFF5_MAXCOL        = BIFF4_MAXCOL;
const sal_Int32 BIFF5_MAXROW        = BIFF4_MAXROW;
const sal_Int16 BIFF5_MAXTAB        = BIFF4_MAXTAB;

const sal_Int32 BIFF8_MAXCOL        = BIFF5_MAXCOL;
const sal_Int32 BIFF8_MAXROW        = 65535;
const sal_Int16 BIFF8_MAXTAB        = BIFF5_MAXTAB;

const sal_Unicode BIFF_URL_DRIVE    = '\x01';       /// DOS drive letter or UNC path.
const sal_Unicode BIFF_URL_ROOT     = '\x02';       /// Root directory of current drive.
const sal_Unicode BIFF_URL_SUBDIR   = '\x03';       /// Subdirectory delimiter.
const sal_Unicode BIFF_URL_PARENT   = '\x04';       /// Parent directory.
const sal_Unicode BIFF_URL_RAW      = '\x05';       /// Unencoded URL.
const sal_Unicode BIFF_URL_INSTALL  = '\x06';       /// Application installation directory.
const sal_Unicode BIFF_URL_INSTALL2 = '\x07';       /// Alternative application installation directory.
const sal_Unicode BIFF_URL_LIBRARY  = '\x08';       /// Library directory in application installation.
const sal_Unicode BIFF4_URL_SHEET   = '\x09';       /// BIFF4 internal sheet.
const sal_Unicode BIFF_URL_UNC      = '@';          /// UNC path root.

const sal_Unicode BIFF_DCON_ENCODED = '\x01';       /// First character of an encoded path from DCON* records.
const sal_Unicode BIFF_DCON_INTERN  = '\x02';       /// First character of an encoded sheet name from DCON* records.


inline sal_uInt8 lclGetBiffAddressSize( bool bCol16Bit, bool bRow32Bit )
{
    return (bCol16Bit ? 2 : 1) + (bRow32Bit ? 4 : 2);
}

inline sal_uInt8 lclGetBiffRangeSize( bool bCol16Bit, bool bRow32Bit )
{
    return 2 * lclGetBiffAddressSize( bCol16Bit, bRow32Bit );
}

} // namespace

// ============================================================================
// ============================================================================

CellAddress ApiCellRangeList::getBaseAddress() const
{
    if( empty() )
        return CellAddress();
    return CellAddress( front().Sheet, front().StartColumn, front().StartRow );
}

// ============================================================================

void BinAddress::read( SequenceInputStream& rStrm )
{
    rStrm >> mnRow >> mnCol;
}

void BinAddress::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit )
{
    mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16();
    mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8();
}

void BinAddress::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const
{
    if( bRow32Bit )
        rStrm << mnRow;
    else
        rStrm << static_cast< sal_uInt16 >( mnRow );
    if( bCol16Bit )
        rStrm << static_cast< sal_uInt16 >( mnCol );
    else
        rStrm << static_cast< sal_uInt8 >( mnCol );
}

// ============================================================================

bool BinRange::contains( const BinAddress& rAddr ) const
{
    return  (maFirst.mnCol <= rAddr.mnCol) && (rAddr.mnCol <= maLast.mnCol) &&
            (maFirst.mnRow <= rAddr.mnRow) && (rAddr.mnRow <= maLast.mnRow);
}

void BinRange::read( SequenceInputStream& rStrm )
{
    rStrm >> maFirst.mnRow >> maLast.mnRow >> maFirst.mnCol >> maLast.mnCol;
}

void BinRange::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit )
{
    maFirst.mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16();
    maLast.mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16();
    maFirst.mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8();
    maLast.mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8();
}

void BinRange::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const
{
    if( bRow32Bit )
        rStrm << maFirst.mnRow << maLast.mnRow;
    else
        rStrm << static_cast< sal_uInt16 >( maFirst.mnRow ) << static_cast< sal_uInt16 >( maLast.mnRow );
    if( bCol16Bit )
        rStrm << static_cast< sal_uInt16 >( maFirst.mnCol ) << static_cast< sal_uInt16 >( maLast.mnCol );
    else
        rStrm << static_cast< sal_uInt8 >( maFirst.mnCol ) << static_cast< sal_uInt8 >( maLast.mnCol );
}

// ============================================================================

BinRange BinRangeList::getEnclosingRange() const
{
    BinRange aRange;
    if( !empty() )
    {
        const_iterator aIt = begin(), aEnd = end();
        aRange = *aIt;
        for( ++aIt; aIt != aEnd; ++aIt )
        {
            aRange.maFirst.mnCol = ::std::min( aRange.maFirst.mnCol, aIt->maFirst.mnCol );
            aRange.maFirst.mnRow = ::std::min( aRange.maFirst.mnRow, aIt->maFirst.mnRow );
            aRange.maLast.mnCol  = ::std::max( aRange.maLast.mnCol, aIt->maLast.mnCol );
            aRange.maLast.mnRow  = ::std::max( aRange.maLast.mnRow, aIt->maLast.mnRow );
        }
    }
    return aRange;
}

void BinRangeList::read( SequenceInputStream& rStrm )
{
    sal_Int32 nCount = rStrm.readInt32();
    resize( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 16 ) );
    for( iterator aIt = begin(), aEnd = end(); aIt != aEnd; ++aIt )
        aIt->read( rStrm );
}

void BinRangeList::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit )
{
    sal_uInt16 nCount = rStrm.readuInt16();
    resize( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / lclGetBiffRangeSize( bCol16Bit, bRow32Bit ) ) );
    for( iterator aIt = begin(), aEnd = end(); aIt != aEnd; ++aIt )
        aIt->read( rStrm, bCol16Bit, bRow32Bit );
}

void BinRangeList::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const
{
    writeSubList( rStrm, 0, size(), bCol16Bit, bRow32Bit );
}

void BinRangeList::writeSubList( BiffOutputStream& rStrm, size_t nBegin, size_t nCount, bool bCol16Bit, bool bRow32Bit ) const
{
    OSL_ENSURE( nBegin <= size(), "BiffRangeList::writeSubList - invalid start position" );
    size_t nEnd = ::std::min< size_t >( nBegin + nCount, size() );
    sal_uInt16 nBiffCount = getLimitedValue< sal_uInt16, size_t >( nEnd - nBegin, 0, SAL_MAX_UINT16 );
    rStrm << nBiffCount;
    rStrm.setPortionSize( lclGetBiffRangeSize( bCol16Bit, bRow32Bit ) );
    for( const_iterator aIt = begin() + nBegin, aEnd = begin() + nEnd; aIt != aEnd; ++aIt )
        aIt->write( rStrm, bCol16Bit, bRow32Bit );
}

// ============================================================================
// ============================================================================

AddressConverter::AddressConverter( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    mbColOverflow( false ),
    mbRowOverflow( false ),
    mbTabOverflow( false )
{
    maDConChars.set( 0xFFFF, '\x01', 0xFFFF, '\x02', 0xFFFF );
    switch( getFilterType() )
    {
        case FILTER_OOXML:
            initializeMaxPos( OOX_MAXTAB, OOX_MAXCOL, OOX_MAXROW );
        break;
        case FILTER_BIFF: switch( getBiff() )
        {
            case BIFF2:
                initializeMaxPos( BIFF2_MAXTAB, BIFF2_MAXCOL, BIFF2_MAXROW );
                maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, 0xFFFF );
            break;
            case BIFF3:
                initializeMaxPos( BIFF3_MAXTAB, BIFF3_MAXCOL, BIFF3_MAXROW );
                maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, 0xFFFF );
            break;
            case BIFF4:
                initializeMaxPos( BIFF4_MAXTAB, BIFF4_MAXCOL, BIFF4_MAXROW );
                maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, '\x00' );
            break;
            case BIFF5:
                initializeMaxPos( BIFF5_MAXTAB, BIFF5_MAXCOL, BIFF5_MAXROW );
                maLinkChars.set( '\x04', '\x01', '\x02', '\x03', '\x00' );
            break;
            case BIFF8:
                initializeMaxPos( BIFF8_MAXTAB, BIFF8_MAXCOL, BIFF8_MAXROW );
                maLinkChars.set( '\x04', '\x01', 0xFFFF, '\x02', '\x00' );
            break;
            case BIFF_UNKNOWN: break;
        }
        break;
        case FILTER_UNKNOWN: break;
    }
}

// ----------------------------------------------------------------------------

bool AddressConverter::parseOoxAddress2d(
        sal_Int32& ornColumn, sal_Int32& ornRow,
        const OUString& rString, sal_Int32 nStart, sal_Int32 nLength )
{
    ornColumn = ornRow = 0;
    if( (nStart < 0) || (nStart >= rString.getLength()) || (nLength < 2) )
        return false;

    const sal_Unicode* pcChar = rString.getStr() + nStart;
    const sal_Unicode* pcEndChar = pcChar + ::std::min( nLength, rString.getLength() - nStart );

    enum { STATE_COL, STATE_ROW } eState = STATE_COL;
    while( pcChar < pcEndChar )
    {
        sal_Unicode cChar = *pcChar;
        switch( eState )
        {
            case STATE_COL:
            {
                if( ('a' <= cChar) && (cChar <= 'z') )
                    (cChar -= 'a') += 'A';
                if( ('A' <= cChar) && (cChar <= 'Z') )
                {
                    /*  Return, if 1-based column index is already 6 characters
                        long (12356631 is column index for column AAAAAA). */
                    if( ornColumn >= 12356631 )
                        return false;
                    (ornColumn *= 26) += (cChar - 'A' + 1);
                }
                else if( ornColumn > 0 )
                {
                    --pcChar;
                    eState = STATE_ROW;
                }
                else
                    return false;
            }
            break;

            case STATE_ROW:
            {
                if( ('0' <= cChar) && (cChar <= '9') )
                {
                    // return, if 1-based row is already 9 digits long
                    if( ornRow >= 100000000 )
                        return false;
                    (ornRow *= 10) += (cChar - '0');
                }
                else
                    return false;
            }
            break;
        }
        ++pcChar;
    }

    --ornColumn;
    --ornRow;
    return (ornColumn >= 0) && (ornRow >= 0);
}

bool AddressConverter::parseOoxRange2d(
        sal_Int32& ornStartColumn, sal_Int32& ornStartRow,
        sal_Int32& ornEndColumn, sal_Int32& ornEndRow,
        const OUString& rString, sal_Int32 nStart, sal_Int32 nLength )
{
    ornStartColumn = ornStartRow = ornEndColumn = ornEndRow = 0;
    if( (nStart < 0) || (nStart >= rString.getLength()) || (nLength < 2) )
        return false;

    sal_Int32 nEnd = nStart + ::std::min( nLength, rString.getLength() - nStart );
    sal_Int32 nColonPos = rString.indexOf( ':', nStart );
    if( (nStart < nColonPos) && (nColonPos + 1 < nEnd) )
    {
        return
            parseOoxAddress2d( ornStartColumn, ornStartRow, rString, nStart, nColonPos - nStart ) &&
            parseOoxAddress2d( ornEndColumn, ornEndRow, rString, nColonPos + 1, nLength - nColonPos - 1 );
    }

    if( parseOoxAddress2d( ornStartColumn, ornStartRow, rString, nStart, nLength ) )
    {
        ornEndColumn = ornStartColumn;
        ornEndRow = ornStartRow;
        return true;
    }

    return false;
}

namespace {

bool lclAppendUrlChar( OUStringBuffer& orUrl, sal_Unicode cChar, bool bEncodeSpecial )
{
    // #126855# encode special characters
    if( bEncodeSpecial ) switch( cChar )
    {
        case '#':   orUrl.appendAscii( "%23" );  return true;
        case '%':   orUrl.appendAscii( "%25" );  return true;
    }
    orUrl.append( cChar );
    return cChar >= ' ';
}

} // namespace

BiffTargetType AddressConverter::parseBiffTargetUrl(
        OUString& orClassName, OUString& orTargetUrl, OUString& orSheetName,
        const OUString& rBiffTargetUrl, bool bFromDConRec )
{
    OUStringBuffer aTargetUrl;
    OUStringBuffer aSheetName;
    // default target type: some URL with/without sheet name, may be overridden below
    BiffTargetType eTargetType = BIFF_TARGETTYPE_URL;
    const ControlCharacters& rCChars = bFromDConRec ? maDConChars : maLinkChars;

    enum
    {
        STATE_START,
        STATE_ENCODED_PATH_START,       /// Start of encoded file path.
        STATE_ENCODED_PATH,             /// Inside encoded file path.
        STATE_ENCODED_DRIVE,            /// DOS drive letter or start of UNC path.
        STATE_ENCODED_URL,              /// Encoded URL, e.g. http links.
        STATE_UNENCODED,                /// Unencoded URL, could be DDE or OLE.
        STATE_DDE_OLE,                  /// Second part of DDE or OLE link.
        STATE_FILENAME,                 /// File name enclosed in brackets.
        STATE_SHEETNAME,                /// Sheet name following enclosed file name.
        STATE_UNSUPPORTED,              /// Unsupported special paths.
        STATE_ERROR
    }
    eState = STATE_START;

    const sal_Unicode* pcChar = rBiffTargetUrl.getStr();
    const sal_Unicode* pcEnd = pcChar + rBiffTargetUrl.getLength();
    for( ; (eState != STATE_ERROR) && (pcChar < pcEnd); ++pcChar )
    {
        sal_Unicode cChar = *pcChar;
        switch( eState )
        {
            case STATE_START:
                if( (cChar == rCChars.mcThisWorkbook) || (cChar == rCChars.mcThisSheet) || (cChar == rCChars.mcSameSheet) )
                {
                    if( pcChar + 1 < pcEnd )
                        eState = STATE_ERROR;
                    if( cChar == rCChars.mcSameSheet )
                        eTargetType = BIFF_TARGETTYPE_SAMESHEET;
                }
                else if( cChar == rCChars.mcExternal )
                    eState = (pcChar + 1 < pcEnd) ? STATE_ENCODED_PATH_START : STATE_ERROR;
                else if( cChar == rCChars.mcInternal )
                    eState = (pcChar + 1 < pcEnd) ? STATE_SHEETNAME : STATE_ERROR;
                else
                    eState = lclAppendUrlChar( aTargetUrl, cChar, true ) ? STATE_UNENCODED : STATE_ERROR;
            break;

            case STATE_ENCODED_PATH_START:
                if( cChar == BIFF_URL_DRIVE )
                    eState = STATE_ENCODED_DRIVE;
                else if( cChar == BIFF_URL_ROOT )
                {
                    aTargetUrl.append( sal_Unicode( '/' ) );
                    eState = STATE_ENCODED_PATH;
                }
                else if( cChar == BIFF_URL_PARENT )
                    aTargetUrl.appendAscii( "../" );
                else if( cChar == BIFF_URL_RAW )
                    eState = STATE_ENCODED_URL;
                else if( cChar == BIFF_URL_INSTALL )
                    eState = STATE_UNSUPPORTED;
                else if( cChar == BIFF_URL_INSTALL2 )
                    eState = STATE_UNSUPPORTED;
                else if( cChar == BIFF_URL_LIBRARY )
                {
                    eState = STATE_ENCODED_PATH;
                    eTargetType = BIFF_TARGETTYPE_LIBRARY;
                }
                else if( (getBiff() == BIFF4) && (cChar == BIFF4_URL_SHEET) )
                    eState = STATE_SHEETNAME;
                else if( cChar == '[' )
                    eState = STATE_FILENAME;
                else if( lclAppendUrlChar( aTargetUrl, cChar, true ) )
                    eState = STATE_ENCODED_PATH;
                else
                    eState = STATE_ERROR;
            break;

            case STATE_ENCODED_PATH:
                if( cChar == BIFF_URL_SUBDIR )
                    aTargetUrl.append( sal_Unicode( '/' ) );
                else if( cChar == '[' )
                    eState = STATE_FILENAME;
                else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) )
                    eState = STATE_ERROR;
            break;

            case STATE_ENCODED_DRIVE:
                if( cChar == BIFF_URL_UNC )
                {
                    aTargetUrl.appendAscii( "file://" );
                    eState = STATE_ENCODED_PATH;
                }
                else
                {
                    aTargetUrl.appendAscii( "file:///" );
                    eState = lclAppendUrlChar( aTargetUrl, cChar, false ) ? STATE_ENCODED_PATH : STATE_ERROR;
                    aTargetUrl.appendAscii( ":/" );
                }
            break;

            case STATE_ENCODED_URL:
            {
                sal_Int32 nLength = cChar;
                if( nLength + 1 == pcEnd - pcChar )
                    aTargetUrl.append( pcChar + 1, nLength );
                else
                    eState = STATE_ERROR;
            }
            break;

            case STATE_UNENCODED:
                if( cChar == BIFF_URL_SUBDIR )
                {
                    orClassName = aTargetUrl.makeStringAndClear();
                    eState = bFromDConRec ? STATE_ERROR : STATE_DDE_OLE;
                    eTargetType = BIFF_TARGETTYPE_DDE_OLE;
                }
                else if( cChar == '[' )
                    eState = STATE_FILENAME;
                else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) )
                    eState = STATE_ERROR;
            break;

            case STATE_DDE_OLE:
                if( !lclAppendUrlChar( aTargetUrl, cChar, true ) )
                    eState = STATE_ERROR;
            break;

            case STATE_FILENAME:
                if( cChar == ']' )
                    eState = STATE_SHEETNAME;
                else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) )
                    eState = STATE_ERROR;
            break;

            case STATE_SHEETNAME:
                if( !lclAppendUrlChar( aSheetName, cChar, false ) )
                    eState = STATE_ERROR;
            break;

            case STATE_UNSUPPORTED:
                pcChar = pcEnd - 1;
            break;

            case STATE_ERROR:
            break;
        }
    }

    OSL_ENSURE( (eState != STATE_ERROR) && (pcChar == pcEnd),
        OStringBuffer( "AddressConverter::parseBiffTargetUrl - parser error in target \"" ).
        append( OUStringToOString( rBiffTargetUrl, RTL_TEXTENCODING_UTF8 ) ).append( '"' ).getStr() );
    bool bParserOk = (eState != STATE_ERROR) && (eState != STATE_UNSUPPORTED) && (pcChar == pcEnd);

    if( bParserOk )
    {
        orTargetUrl = aTargetUrl.makeStringAndClear();
        orSheetName = aSheetName.makeStringAndClear();
    }
    else
    {
        orClassName = orTargetUrl = orSheetName = OUString();
    }

    return bParserOk ? eTargetType : BIFF_TARGETTYPE_UNKNOWN;
}

// ----------------------------------------------------------------------------

bool AddressConverter::checkCol( sal_Int32 nCol, bool bTrackOverflow )
{
    bool bValid = (0 <= nCol) && (nCol <= maMaxPos.Column);
    if( !bValid && bTrackOverflow )
        mbColOverflow = true;
    return bValid;
}

bool AddressConverter::checkRow( sal_Int32 nRow, bool bTrackOverflow )
{
    bool bValid = (0 <= nRow) && (nRow <= maMaxPos.Row);
    if( !bValid && bTrackOverflow )
        mbRowOverflow = true;
    return bValid;
}

bool AddressConverter::checkTab( sal_Int16 nSheet, bool bTrackOverflow )
{
    bool bValid = (0 <= nSheet) && (nSheet <= maMaxPos.Sheet);
    if( !bValid && bTrackOverflow )
        mbTabOverflow |= (nSheet > maMaxPos.Sheet);  // do not warn for deleted refs (-1)
    return bValid;
}

// ----------------------------------------------------------------------------

bool AddressConverter::checkCellAddress( const CellAddress& rAddress, bool bTrackOverflow )
{
    return
        checkTab( rAddress.Sheet, bTrackOverflow ) &&
        checkCol( rAddress.Column, bTrackOverflow ) &&
        checkRow( rAddress.Row, bTrackOverflow );
}

bool AddressConverter::convertToCellAddressUnchecked( CellAddress& orAddress,
        const OUString& rString, sal_Int16 nSheet )
{
    orAddress.Sheet = nSheet;
    return parseOoxAddress2d( orAddress.Column, orAddress.Row, rString );
}

bool AddressConverter::convertToCellAddress( CellAddress& orAddress,
        const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow )
{
    return
        convertToCellAddressUnchecked( orAddress, rString, nSheet ) &&
        checkCellAddress( orAddress, bTrackOverflow );
}

CellAddress AddressConverter::createValidCellAddress(
        const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow )
{
    CellAddress aAddress;
    if( !convertToCellAddress( aAddress, rString, nSheet, bTrackOverflow ) )
    {
        aAddress.Sheet  = getLimitedValue< sal_Int16, sal_Int16 >( nSheet, 0, maMaxPos.Sheet );
        aAddress.Column = ::std::min( aAddress.Column, maMaxPos.Column );
        aAddress.Row    = ::std::min( aAddress.Row, maMaxPos.Row );
    }
    return aAddress;
}

void AddressConverter::convertToCellAddressUnchecked( CellAddress& orAddress,
        const BinAddress& rBinAddress, sal_Int16 nSheet )
{
    orAddress.Sheet  = nSheet;
    orAddress.Column = rBinAddress.mnCol;
    orAddress.Row    = rBinAddress.mnRow;
}

bool AddressConverter::convertToCellAddress( CellAddress& orAddress,
        const BinAddress& rBinAddress, sal_Int16 nSheet, bool bTrackOverflow )
{
    convertToCellAddressUnchecked( orAddress, rBinAddress, nSheet );
    return checkCellAddress( orAddress, bTrackOverflow );
}

CellAddress AddressConverter::createValidCellAddress(
        const BinAddress& rBinAddress, sal_Int16 nSheet, bool bTrackOverflow )
{
    CellAddress aAddress;
    if( !convertToCellAddress( aAddress, rBinAddress, nSheet, bTrackOverflow ) )
    {
        aAddress.Sheet  = getLimitedValue< sal_Int16, sal_Int16 >( nSheet, 0, maMaxPos.Sheet );
        aAddress.Column = getLimitedValue< sal_Int32, sal_Int32 >( rBinAddress.mnCol, 0, maMaxPos.Column );
        aAddress.Row    = getLimitedValue< sal_Int32, sal_Int32 >( rBinAddress.mnRow, 0, maMaxPos.Row );
    }
    return aAddress;
}

// ----------------------------------------------------------------------------

bool AddressConverter::checkCellRange( const CellRangeAddress& rRange, bool bAllowOverflow, bool bTrackOverflow )
{
    return
        (checkCol( rRange.EndColumn, bTrackOverflow ) || bAllowOverflow) &&     // bAllowOverflow after checkCol to track overflow!
        (checkRow( rRange.EndRow, bTrackOverflow ) || bAllowOverflow) &&        // bAllowOverflow after checkRow to track overflow!
        checkTab( rRange.Sheet, bTrackOverflow ) &&
        checkCol( rRange.StartColumn, bTrackOverflow ) &&
        checkRow( rRange.StartRow, bTrackOverflow );
}

bool AddressConverter::validateCellRange( CellRangeAddress& orRange, bool bAllowOverflow, bool bTrackOverflow )
{
    if( orRange.StartColumn > orRange.EndColumn )
        ::std::swap( orRange.StartColumn, orRange.EndColumn );
    if( orRange.StartRow > orRange.EndRow )
        ::std::swap( orRange.StartRow, orRange.EndRow );
    if( !checkCellRange( orRange, bAllowOverflow, bTrackOverflow ) )
        return false;
    if( orRange.EndColumn > maMaxPos.Column )
        orRange.EndColumn = maMaxPos.Column;
    if( orRange.EndRow > maMaxPos.Row )
        orRange.EndRow = maMaxPos.Row;
    return true;
}

bool AddressConverter::convertToCellRangeUnchecked( CellRangeAddress& orRange,
        const OUString& rString, sal_Int16 nSheet )
{
    orRange.Sheet = nSheet;
    return parseOoxRange2d( orRange.StartColumn, orRange.StartRow, orRange.EndColumn, orRange.EndRow, rString );
}

bool AddressConverter::convertToCellRange( CellRangeAddress& orRange,
        const OUString& rString, sal_Int16 nSheet, bool bAllowOverflow, bool bTrackOverflow )
{
    return
        convertToCellRangeUnchecked( orRange, rString, nSheet ) &&
        validateCellRange( orRange, bAllowOverflow, bTrackOverflow );
}

void AddressConverter::convertToCellRangeUnchecked( CellRangeAddress& orRange,
        const BinRange& rBinRange, sal_Int16 nSheet )
{
    orRange.Sheet       = nSheet;
    orRange.StartColumn = rBinRange.maFirst.mnCol;
    orRange.StartRow    = rBinRange.maFirst.mnRow;
    orRange.EndColumn   = rBinRange.maLast.mnCol;
    orRange.EndRow      = rBinRange.maLast.mnRow;
}

bool AddressConverter::convertToCellRange( CellRangeAddress& orRange,
        const BinRange& rBinRange, sal_Int16 nSheet, bool bAllowOverflow, bool bTrackOverflow )
{
    convertToCellRangeUnchecked( orRange, rBinRange, nSheet );
    return validateCellRange( orRange, bAllowOverflow, bTrackOverflow );
}

// ----------------------------------------------------------------------------

bool AddressConverter::checkCellRangeList( const ApiCellRangeList& rRanges, bool bAllowOverflow, bool bTrackOverflow )
{
    for( ApiCellRangeList::const_iterator aIt = rRanges.begin(), aEnd = rRanges.end(); aIt != aEnd; ++aIt )
        if( !checkCellRange( *aIt, bAllowOverflow, bTrackOverflow ) )
            return false;
    return true;
}

void AddressConverter::validateCellRangeList( ApiCellRangeList& orRanges, bool bTrackOverflow )
{
    for( size_t nIndex = orRanges.size(); nIndex > 0; --nIndex )
        if( !validateCellRange( orRanges[ nIndex - 1 ], true, bTrackOverflow ) )
            orRanges.erase( orRanges.begin() + nIndex - 1 );
}

void AddressConverter::convertToCellRangeList( ApiCellRangeList& orRanges,
        const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow )
{
    sal_Int32 nPos = 0;
    sal_Int32 nLen = rString.getLength();
    CellRangeAddress aRange;
    while( (0 <= nPos) && (nPos < nLen) )
    {
        OUString aToken = rString.getToken( 0, ' ', nPos );
        if( (aToken.getLength() > 0) && convertToCellRange( aRange, aToken, nSheet, true, bTrackOverflow ) )
            orRanges.push_back( aRange );
    }
}

void AddressConverter::convertToCellRangeList( ApiCellRangeList& orRanges,
        const BinRangeList& rBinRanges, sal_Int16 nSheet, bool bTrackOverflow )
{
    CellRangeAddress aRange;
    for( BinRangeList::const_iterator aIt = rBinRanges.begin(), aEnd = rBinRanges.end(); aIt != aEnd; ++aIt )
        if( convertToCellRange( aRange, *aIt, nSheet, true, bTrackOverflow ) )
            orRanges.push_back( aRange );
}

// private --------------------------------------------------------------------

void AddressConverter::ControlCharacters::set(
        sal_Unicode cThisWorkbook, sal_Unicode cExternal,
        sal_Unicode cThisSheet, sal_Unicode cInternal, sal_Unicode cSameSheet )
{
    mcThisWorkbook = cThisWorkbook;
    mcExternal     = cExternal;
    mcThisSheet    = cThisSheet;
    mcInternal     = cInternal;
    mcSameSheet    = cSameSheet;
}

void AddressConverter::initializeMaxPos(
        sal_Int16 nMaxXlsTab, sal_Int32 nMaxXlsCol, sal_Int32 nMaxXlsRow )
{
    maMaxXlsPos.Sheet  = nMaxXlsTab;
    maMaxXlsPos.Column = nMaxXlsCol;
    maMaxXlsPos.Row    = nMaxXlsRow;

    // maximum cell position in Calc
    try
    {
        Reference< XIndexAccess > xSheetsIA( getDocument()->getSheets(), UNO_QUERY_THROW );
        Reference< XCellRangeAddressable > xAddressable( xSheetsIA->getByIndex( 0 ), UNO_QUERY_THROW );
        CellRangeAddress aRange = xAddressable->getRangeAddress();
        maMaxApiPos = CellAddress( API_MAXTAB, aRange.EndColumn, aRange.EndRow );
        maMaxPos = getBaseFilter().isImportFilter() ? maMaxApiPos : maMaxXlsPos;
    }
    catch( Exception& )
    {
        OSL_ENSURE( false, "AddressConverter::AddressConverter - cannot get sheet limits" );
    }
}

// ============================================================================

} // namespace xls
} // namespace oox
