/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "oox/dump/dumperbase.hxx"

#include <algorithm>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/ucb/XSimpleFileAccess.hpp>
#include <com/sun/star/io/XActiveDataSink.hpp>
#include <com/sun/star/io/XActiveDataSource.hpp>
#include <com/sun/star/io/XTextInputStream.hpp>
#include <com/sun/star/io/XTextOutputStream.hpp>
#include <rtl/math.hxx>
#include <osl/file.hxx>
#include <comphelper/docpasswordhelper.hxx>
#include "oox/helper/binaryoutputstream.hxx"
#include "oox/helper/textinputstream.hxx"
#include "oox/core/filterbase.hxx"
#include "oox/xls/biffhelper.hxx"

#if OOX_INCLUDE_DUMPER

using ::rtl::OString;
using ::rtl::OStringBuffer;
using ::rtl::OStringToOUString;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;
using ::rtl::OUStringToOString;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::util::DateTime;
using ::com::sun::star::lang::XMultiServiceFactory;
using ::com::sun::star::ucb::XSimpleFileAccess;
using ::com::sun::star::io::XActiveDataSink;
using ::com::sun::star::io::XActiveDataSource;
using ::com::sun::star::io::XInputStream;
using ::com::sun::star::io::XOutputStream;
using ::com::sun::star::io::XTextInputStream;
using ::com::sun::star::io::XTextOutputStream;
using ::comphelper::MediaDescriptor;
using ::oox::core::FilterBase;

namespace oox {
namespace dump {

const sal_Unicode OOX_DUMP_BOM          = 0xFEFF;
const sal_Int32 OOX_DUMP_MAXSTRLEN      = 80;
const sal_Int32 OOX_DUMP_INDENT         = 2;
const sal_Unicode OOX_DUMP_BINDOT       = '.';
const sal_Unicode OOX_DUMP_CFG_LISTSEP  = ',';
const sal_Unicode OOX_DUMP_CFG_QUOTE    = '\'';
const sal_Unicode OOX_DUMP_LF           = '\n';
const sal_Unicode OOX_DUMP_ITEMSEP      = '=';
const sal_Int32 OOX_DUMP_BYTESPERLINE   = 16;
const sal_Int64 OOX_DUMP_MAXARRAY       = 16;

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

// file names -----------------------------------------------------------------

OUString InputOutputHelper::convertFileNameToUrl( const OUString& rFileName )
{
    OUString aFileUrl;
    if( ::osl::FileBase::getFileURLFromSystemPath( rFileName, aFileUrl ) == ::osl::FileBase::E_None )
        return aFileUrl;
    return OUString();
}

sal_Int32 InputOutputHelper::getFileNamePos( const OUString& rFileUrl )
{
    sal_Int32 nSepPos = rFileUrl.lastIndexOf( '/' );
    return (nSepPos < 0) ? 0 : (nSepPos + 1);
}

OUString InputOutputHelper::getFileNameExtension( const OUString& rFileUrl )
{
    sal_Int32 nNamePos = getFileNamePos( rFileUrl );
    sal_Int32 nExtPos = rFileUrl.lastIndexOf( '.' );
    if( nExtPos >= nNamePos )
        return rFileUrl.copy( nExtPos + 1 );
    return OUString();
}

// input streams --------------------------------------------------------------

Reference< XInputStream > InputOutputHelper::getXInputStream( BinaryInputStream& rStrm )
{
    if( BinaryXInputStream* pXStrm = dynamic_cast< BinaryXInputStream* >( &rStrm ) )
        return pXStrm->getXInputStream();
    return 0;
}

Reference< XInputStream > InputOutputHelper::openInputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const OUString& rFileName )
{
    Reference< XInputStream > xInStrm;
    if( rxFactory.is() ) try
    {
        Reference< XSimpleFileAccess > xFileAccess( rxFactory->createInstance( CREATE_OUSTRING( "com.sun.star.ucb.SimpleFileAccess" ) ), UNO_QUERY_THROW );
        xInStrm = xFileAccess->openFileRead( rFileName );
    }
    catch( Exception& )
    {
    }
    return xInStrm;
}

Reference< XTextInputStream > InputOutputHelper::openTextInputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const Reference< XInputStream >& rxInStrm, const OUString& rEncoding )
{
    Reference< XTextInputStream > xTextInStrm;
    if( rxFactory.is() && rxInStrm.is() ) try
    {
        Reference< XActiveDataSink > xDataSink( rxFactory->createInstance( CREATE_OUSTRING( "com.sun.star.io.TextInputStream" ) ), UNO_QUERY_THROW );
        xDataSink->setInputStream( rxInStrm );
        xTextInStrm.set( xDataSink, UNO_QUERY_THROW );
        xTextInStrm->setEncoding( rEncoding );
    }
    catch( Exception& )
    {
    }
    return xTextInStrm;
}

Reference< XTextInputStream > InputOutputHelper::openTextInputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const OUString& rFileName, const OUString& rEncoding )
{
    return openTextInputStream( rxFactory, openInputStream( rxFactory, rFileName ), rEncoding );
}

// output streams -------------------------------------------------------------

Reference< XOutputStream > InputOutputHelper::openOutputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const OUString& rFileName )
{
    Reference< XOutputStream > xOutStrm;
    if( rxFactory.is() ) try
    {
        Reference< XSimpleFileAccess > xFileAccess( rxFactory->createInstance( CREATE_OUSTRING( "com.sun.star.ucb.SimpleFileAccess" ) ), UNO_QUERY_THROW );
        xOutStrm = xFileAccess->openFileWrite( rFileName );
    }
    catch( Exception& )
    {
    }
    return xOutStrm;
}

Reference< XTextOutputStream > InputOutputHelper::openTextOutputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const Reference< XOutputStream >& rxOutStrm, const OUString& rEncoding )
{
    Reference< XTextOutputStream > xTextOutStrm;
    if( rxFactory.is() && rxOutStrm.is() ) try
    {
        Reference< XActiveDataSource > xDataSource( rxFactory->createInstance( CREATE_OUSTRING( "com.sun.star.io.TextOutputStream" ) ), UNO_QUERY_THROW );
        xDataSource->setOutputStream( rxOutStrm );
        xTextOutStrm.set( xDataSource, UNO_QUERY_THROW );
        xTextOutStrm->setEncoding( rEncoding );
    }
    catch( Exception& )
    {
    }
    return xTextOutStrm;
}

Reference< XTextOutputStream > InputOutputHelper::openTextOutputStream(
        const Reference< XMultiServiceFactory >& rxFactory, const OUString& rFileName, const OUString& rEncoding )
{
    return openTextOutputStream( rxFactory, openOutputStream( rxFactory, rFileName ), rEncoding );
}

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

ItemFormat::ItemFormat() :
    meDataType( DATATYPE_VOID ),
    meFmtType( FORMATTYPE_NONE )
{
}

void ItemFormat::set( DataType eDataType, FormatType eFmtType, const OUString& rItemName )
{
    meDataType = eDataType;
    meFmtType = eFmtType;
    maItemName = rItemName;
    maListName = OUString();
}

void ItemFormat::set( DataType eDataType, FormatType eFmtType, const OUString& rItemName, const OUString& rListName )
{
    set( eDataType, eFmtType, rItemName );
    maListName = rListName;
}

OUStringVector::const_iterator ItemFormat::parse( const OUStringVector& rFormatVec )
{
    set( DATATYPE_VOID, FORMATTYPE_NONE, OUString() );

    OUStringVector::const_iterator aIt = rFormatVec.begin(), aEnd = rFormatVec.end();
    OUString aDataType, aFmtType;
    if( aIt != aEnd ) aDataType = *aIt++;
    if( aIt != aEnd ) aFmtType = *aIt++;
    if( aIt != aEnd ) maItemName = *aIt++;
    if( aIt != aEnd ) maListName = *aIt++;

    meDataType = StringHelper::convertToDataType( aDataType );
    meFmtType = StringHelper::convertToFormatType( aFmtType );

    if( meFmtType == FORMATTYPE_NONE )
    {
        if( aFmtType.equalsAscii( "unused" ) )
            set( meDataType, FORMATTYPE_HEX, CREATE_OUSTRING( OOX_DUMP_UNUSED ) );
        else if( aFmtType.equalsAscii( "unknown" ) )
            set( meDataType, FORMATTYPE_HEX, CREATE_OUSTRING( OOX_DUMP_UNKNOWN ) );
    }

    return aIt;
}

OUStringVector ItemFormat::parse( const OUString& rFormatStr )
{
    OUStringVector aFormatVec;
    StringHelper::convertStringToStringList( aFormatVec, rFormatStr, false );
    OUStringVector::const_iterator aIt = parse( aFormatVec );
    return OUStringVector( aIt, const_cast< const OUStringVector& >( aFormatVec ).end() );
}

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

// append string to string ----------------------------------------------------

void StringHelper::appendChar( OUStringBuffer& rStr, sal_Unicode cChar, sal_Int32 nCount )
{
    for( sal_Int32 nIndex = 0; nIndex < nCount; ++nIndex )
        rStr.append( cChar );
}

void StringHelper::appendString( OUStringBuffer& rStr, const OUString& rData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendChar( rStr, cFill, nWidth - rData.getLength() );
    rStr.append( rData );
}

// append decimal -------------------------------------------------------------

void StringHelper::appendDec( OUStringBuffer& rStr, sal_uInt8 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( static_cast< sal_Int32 >( nData ) ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_Int8 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( static_cast< sal_Int32 >( nData ) ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_uInt16 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( static_cast< sal_Int32 >( nData ) ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_Int16 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( static_cast< sal_Int32 >( nData ) ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_uInt32 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( static_cast< sal_Int64 >( nData ) ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_Int32 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( nData ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_uInt64 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    /*  Values greater than biggest signed 64bit integer will change to
        negative when converting to sal_Int64. Therefore, the trailing digit
        will be written separately. */
    OUStringBuffer aBuffer;
    if( nData > 9 )
        aBuffer.append( OUString::valueOf( static_cast< sal_Int64 >( nData / 10 ) ) );
    aBuffer.append( static_cast< sal_Unicode >( '0' + (nData % 10) ) );
    appendString( rStr, aBuffer.makeStringAndClear(), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, sal_Int64 nData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, OUString::valueOf( nData ), nWidth, cFill );
}

void StringHelper::appendDec( OUStringBuffer& rStr, double fData, sal_Int32 nWidth, sal_Unicode cFill )
{
    appendString( rStr, ::rtl::math::doubleToUString( fData, rtl_math_StringFormat_G, 15, '.', true ), nWidth, cFill );
}

// append hexadecimal ---------------------------------------------------------

void StringHelper::appendHex( OUStringBuffer& rStr, sal_uInt8 nData, bool bPrefix )
{
    static const sal_Unicode spcHexDigits[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
    if( bPrefix )
        rStr.appendAscii( "0x" );
    rStr.append( spcHexDigits[ (nData >> 4) & 0x0F ] ).append( spcHexDigits[ nData & 0x0F ] );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_Int8 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt8 >( nData ), bPrefix );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_uInt16 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt8 >( nData >> 8 ), bPrefix );
    appendHex( rStr, static_cast< sal_uInt8 >( nData ), false );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_Int16 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt16 >( nData ), bPrefix );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_uInt32 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt16 >( nData >> 16 ), bPrefix );
    appendHex( rStr, static_cast< sal_uInt16 >( nData ), false );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_Int32 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt32 >( nData ), bPrefix );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_uInt64 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt32 >( nData >> 32 ), bPrefix );
    appendHex( rStr, static_cast< sal_uInt32 >( nData ), false );
}

void StringHelper::appendHex( OUStringBuffer& rStr, sal_Int64 nData, bool bPrefix )
{
    appendHex( rStr, static_cast< sal_uInt64 >( nData ), bPrefix );
}

void StringHelper::appendHex( OUStringBuffer& rStr, double fData, bool bPrefix )
{
    appendHex( rStr, *reinterpret_cast< const sal_uInt64* >( &fData ), bPrefix );
}

// append shortened hexadecimal -----------------------------------------------

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_uInt8 nData, bool bPrefix )
{
    appendHex( rStr, nData, bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_Int8 nData, bool bPrefix )
{
    appendHex( rStr, nData, bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_uInt16 nData, bool bPrefix )
{
    if( nData > SAL_MAX_UINT8 )
        appendHex( rStr, nData, bPrefix );
    else
        appendHex( rStr, static_cast< sal_uInt8 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_Int16 nData, bool bPrefix )
{
    appendShortHex( rStr, static_cast< sal_uInt16 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_uInt32 nData, bool bPrefix )
{
    if( nData > SAL_MAX_UINT16 )
        appendHex( rStr, nData, bPrefix );
    else
        appendShortHex( rStr, static_cast< sal_uInt16 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_Int32 nData, bool bPrefix )
{
    appendShortHex( rStr, static_cast< sal_uInt32 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_uInt64 nData, bool bPrefix )
{
    if( nData > SAL_MAX_UINT32 )
        appendHex( rStr, nData, bPrefix );
    else
        appendShortHex( rStr, static_cast< sal_uInt32 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, sal_Int64 nData, bool bPrefix )
{
    appendShortHex( rStr, static_cast< sal_uInt64 >( nData ), bPrefix );
}

void StringHelper::appendShortHex( OUStringBuffer& rStr, double fData, bool bPrefix )
{
    appendHex( rStr, fData, bPrefix );
}

// append binary --------------------------------------------------------------

void StringHelper::appendBin( OUStringBuffer& rStr, sal_uInt8 nData, bool bDots )
{
    for( sal_uInt8 nMask = 0x80; nMask != 0; (nMask >>= 1) &= 0x7F )
    {
        rStr.append( static_cast< sal_Unicode >( (nData & nMask) ? '1' : '0' ) );
        if( bDots && (nMask == 0x10) )
            rStr.append( OOX_DUMP_BINDOT );
    }
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_Int8 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt8 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_uInt16 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt8 >( nData >> 8 ), bDots );
    if( bDots )
        rStr.append( OOX_DUMP_BINDOT );
    appendBin( rStr, static_cast< sal_uInt8 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_Int16 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt16 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_uInt32 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt16 >( nData >> 16 ), bDots );
    if( bDots )
        rStr.append( OOX_DUMP_BINDOT );
    appendBin( rStr, static_cast< sal_uInt16 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_Int32 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt32 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_uInt64 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt32 >( nData >> 32 ), bDots );
    if( bDots )
        rStr.append( OOX_DUMP_BINDOT );
    appendBin( rStr, static_cast< sal_uInt32 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, sal_Int64 nData, bool bDots )
{
    appendBin( rStr, static_cast< sal_uInt64 >( nData ), bDots );
}

void StringHelper::appendBin( OUStringBuffer& rStr, double fData, bool bDots )
{
    appendBin( rStr, *reinterpret_cast< const sal_uInt64* >( &fData ), bDots );
}

// append formatted value -----------------------------------------------------

void StringHelper::appendBool( OUStringBuffer& rStr, bool bData )
{
    rStr.appendAscii( bData ? "true" : "false" );
}

// append columns, rows, addresses --------------------------------------------

void StringHelper::appendAddrCol( OUStringBuffer& rStr, sal_Int32 nCol, bool bRel )
{
    if( !bRel ) rStr.append( OOX_DUMP_ADDRABS );
    sal_Int32 nPos = rStr.getLength();
    for( sal_Int32 nTemp = nCol; nTemp >= 0; (nTemp /= 26) -= 1 )
        rStr.insert( nPos, static_cast< sal_Unicode >( 'A' + (nTemp % 26) ) );
}

void StringHelper::appendAddrRow( OUStringBuffer& rStr, sal_Int32 nRow, bool bRel )
{
    if( !bRel ) rStr.append( OOX_DUMP_ADDRABS );
    appendDec( rStr, nRow + 1 );
}

void StringHelper::appendAddrName( OUStringBuffer& rStr, sal_Unicode cPrefix, sal_Int32 nColRow, bool bRel )
{
    rStr.append( cPrefix );
    if( bRel && (nColRow != 0) )
    {
        rStr.append( OOX_DUMP_R1C1OPEN );
        appendDec( rStr, nColRow );
        rStr.append( OOX_DUMP_R1C1CLOSE );
    }
    else if( !bRel )
        appendDec( rStr, nColRow + 1 );
}

void StringHelper::appendAddress( OUStringBuffer& rStr, const Address& rPos )
{
    appendAddrCol( rStr, rPos.mnCol, true );
    appendAddrRow( rStr, rPos.mnRow, true );
}

void StringHelper::appendRange( OUStringBuffer& rStr, const Range& rRange )
{
    appendAddress( rStr, rRange.maFirst );
    rStr.append( OOX_DUMP_RANGESEP );
    appendAddress( rStr, rRange.maLast );
}

void StringHelper::appendRangeList( OUStringBuffer& rStr, const RangeList& rRanges )
{
    OUStringBuffer aData;
    for( RangeList::const_iterator aIt = rRanges.begin(), aEnd = rRanges.end(); aIt != aEnd; ++aIt )
    {
        OUStringBuffer aRange;
        appendRange( aRange, *aIt );
        appendToken( aData, aRange.makeStringAndClear(), OOX_DUMP_LISTSEP );
    }
    rStr.append( aData.makeStringAndClear() );
}

void StringHelper::appendAddress( OUStringBuffer& rStr, const TokenAddress& rPos, bool bR1C1 )
{
    if( bR1C1 && (rPos.mbRelCol || rPos.mbRelRow) )
    {
        appendAddrName( rStr, OOX_DUMP_R1C1ROW, rPos.mnRow, rPos.mbRelRow );
        appendAddrName( rStr, OOX_DUMP_R1C1COL, rPos.mnCol, rPos.mbRelCol );
    }
    else
    {
        appendAddrCol( rStr, rPos.mnCol, rPos.mbRelCol );
        appendAddrRow( rStr, rPos.mnRow, rPos.mbRelRow );
    }
}

void StringHelper::appendRange( OUStringBuffer& rStr, const TokenRange& rRange, bool bR1C1 )
{
    appendAddress( rStr, rRange.maFirst, bR1C1 );
    rStr.append( OOX_DUMP_RANGESEP );
    appendAddress( rStr, rRange.maLast, bR1C1 );
}

// encoded text output --------------------------------------------------------

void StringHelper::appendCChar( OUStringBuffer& rStr, sal_Unicode cChar, bool bPrefix )
{
    if( cChar > 0x00FF )
    {
        if( bPrefix )
            rStr.appendAscii( "\\u" );
        appendHex( rStr, static_cast< sal_uInt16 >( cChar ), false );
    }
    else
    {
        if( bPrefix )
            rStr.appendAscii( "\\x" );
        appendHex( rStr, static_cast< sal_uInt8 >( cChar ), false );
    }
}

void StringHelper::appendEncChar( OUStringBuffer& rStr, sal_Unicode cChar, sal_Int32 nCount, bool bPrefix )
{
    if( cChar < 0x0020 )
    {
        // C-style hex code
        OUStringBuffer aCode;
        appendCChar( aCode, cChar, bPrefix );
        for( sal_Int32 nIdx = 0; nIdx < nCount; ++nIdx )
            rStr.append( aCode );
    }
    else
    {
        appendChar( rStr, cChar, nCount );
    }
}

void StringHelper::appendEncString( OUStringBuffer& rStr, const OUString& rData, bool bPrefix )
{
    sal_Int32 nBeg = 0;
    sal_Int32 nIdx = 0;
    sal_Int32 nEnd = rData.getLength();
    while( nIdx < nEnd )
    {
        // find next character that needs encoding
        while( (nIdx < nEnd) && (rData[ nIdx ] >= 0x20) ) ++nIdx;
        // append portion
        if( nBeg < nIdx )
        {
            if( (nBeg == 0) && (nIdx == nEnd) )
                rStr.append( rData );
            else
                rStr.append( rData.copy( nBeg, nIdx - nBeg ) );
        }
        // append characters to be encoded
        while( (nIdx < nEnd) && (rData[ nIdx ] < 0x20) )
        {
            appendCChar( rStr, rData[ nIdx ], bPrefix );
            ++nIdx;
        }
        // adjust limits
        nBeg = nIdx;
    }
}

// token list -----------------------------------------------------------------

void StringHelper::appendToken( OUStringBuffer& rStr, const OUString& rToken, sal_Unicode cSep )
{
    if( (rStr.getLength() > 0) && (rToken.getLength() > 0) )
        rStr.append( cSep );
    rStr.append( rToken );
}

void StringHelper::appendToken( OUStringBuffer& rStr, sal_Int64 nToken, sal_Unicode cSep )
{
    OUStringBuffer aToken;
    appendDec( aToken, nToken );
    appendToken( rStr, aToken.makeStringAndClear(), cSep );
}

void StringHelper::prependToken( OUStringBuffer& rStr, const OUString& rToken, sal_Unicode cSep )
{
    if( (rStr.getLength() > 0) && (rToken.getLength() > 0) )
        rStr.insert( 0, cSep );
    rStr.insert( 0, rToken );
}

void StringHelper::prependToken( OUStringBuffer& rStr, sal_Int64 nToken, sal_Unicode cSep )
{
    OUStringBuffer aToken;
    appendDec( aToken, nToken );
    prependToken( rStr, aToken.makeStringAndClear(), cSep );
}

void StringHelper::appendIndex( OUStringBuffer& rStr, const OUString& rIdx )
{
    rStr.append( sal_Unicode( '[' ) ).append( rIdx ).append( sal_Unicode( ']' ) );
}

void StringHelper::appendIndex( OUStringBuffer& rStr, sal_Int64 nIdx )
{
    OUStringBuffer aToken;
    appendDec( aToken, nIdx );
    appendIndex( rStr, aToken.makeStringAndClear() );
}

void StringHelper::appendIndexedText( OUStringBuffer& rStr, const OUString& rData, const OUString& rIdx )
{
    rStr.append( rData );
    appendIndex( rStr, rIdx );
}

void StringHelper::appendIndexedText( OUStringBuffer& rStr, const OUString& rData, sal_Int64 nIdx )
{
    rStr.append( rData );
    appendIndex( rStr, nIdx );
}

OUString StringHelper::getToken( const OUString& rData, sal_Int32& rnPos, sal_Unicode cSep )
{
    return trimSpaces( rData.getToken( 0, cSep, rnPos ) );
}

void StringHelper::enclose( OUStringBuffer& rStr, sal_Unicode cOpen, sal_Unicode cClose )
{
    rStr.insert( 0, cOpen ).append( cClose ? cClose : cOpen );
}

// string conversion ----------------------------------------------------------

namespace {

sal_Int32 lclIndexOf( const OUString& rStr, sal_Unicode cChar, sal_Int32 nStartPos )
{
    sal_Int32 nIndex = rStr.indexOf( cChar, nStartPos );
    return (nIndex < 0) ? rStr.getLength() : nIndex;
}

OUString lclTrimQuotedStringList( const OUString& rStr )
{
    OUStringBuffer aBuffer;
    sal_Int32 nPos = 0;
    sal_Int32 nLen = rStr.getLength();
    while( nPos < nLen )
    {
        if( rStr[ nPos ] == OOX_DUMP_CFG_QUOTE )
        {
            // quoted string, skip leading quote character
            ++nPos;
            // process quoted text and ambedded literal quote characters
            OUStringBuffer aToken;
            do
            {
                // seek to next quote character and add text portion to token buffer
                sal_Int32 nEnd = lclIndexOf( rStr, OOX_DUMP_CFG_QUOTE, nPos );
                aToken.append( rStr.copy( nPos, nEnd - nPos ) );
                // process literal quotes
                while( (nEnd + 1 < nLen) && (rStr[ nEnd ] == OOX_DUMP_CFG_QUOTE) && (rStr[ nEnd + 1 ] == OOX_DUMP_CFG_QUOTE) )
                {
                    aToken.append( OOX_DUMP_CFG_QUOTE );
                    nEnd += 2;
                }
                // nEnd is start of possible next text portion
                nPos = nEnd;
            }
            while( (nPos < nLen) && (rStr[ nPos ] != OOX_DUMP_CFG_QUOTE) );
            // add token, seek to list separator, ignore text following closing quote
            aBuffer.append( aToken.makeStringAndClear() );
            nPos = lclIndexOf( rStr, OOX_DUMP_CFG_LISTSEP, nPos );
            if( nPos < nLen )
                aBuffer.append( OOX_DUMP_LF );
            // set current position behind list separator
            ++nPos;
        }
        else
        {
            // find list separator, add token text to buffer
            sal_Int32 nEnd = lclIndexOf( rStr, OOX_DUMP_CFG_LISTSEP, nPos );
            aBuffer.append( rStr.copy( nPos, nEnd - nPos ) );
            if( nEnd < nLen )
                aBuffer.append( OOX_DUMP_LF );
            // set current position behind list separator
            nPos = nEnd + 1;
        }
    }

    return aBuffer.makeStringAndClear();
}

} // namespace

OUString StringHelper::trimSpaces( const OUString& rStr )
{
    sal_Int32 nBeg = 0;
    while( (nBeg < rStr.getLength()) && ((rStr[ nBeg ] == ' ') || (rStr[ nBeg ] == '\t')) )
        ++nBeg;
    sal_Int32 nEnd = rStr.getLength();
    while( (nEnd > nBeg) && ((rStr[ nEnd - 1 ] == ' ') || (rStr[ nEnd - 1 ] == '\t')) )
        --nEnd;
    return rStr.copy( nBeg, nEnd - nBeg );
}

OUString StringHelper::trimTrailingNul( const OUString& rStr )
{
    sal_Int32 nLastPos = rStr.getLength() - 1;
    if( (nLastPos >= 0) && (rStr[ nLastPos ] == 0) )
        return rStr.copy( 0, nLastPos );
    return rStr;
}

OString StringHelper::convertToUtf8( const OUString& rStr )
{
    return OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 );
}

DataType StringHelper::convertToDataType( const OUString& rStr )
{
    DataType eType = DATATYPE_VOID;
    if( rStr.equalsAscii( "int8" ) )
        eType = DATATYPE_INT8;
    else if( rStr.equalsAscii( "uint8" ) )
        eType = DATATYPE_UINT8;
    else if( rStr.equalsAscii( "int16" ) )
        eType = DATATYPE_INT16;
    else if( rStr.equalsAscii( "uint16" ) )
        eType = DATATYPE_UINT16;
    else if( rStr.equalsAscii( "int32" ) )
        eType = DATATYPE_INT32;
    else if( rStr.equalsAscii( "uint32" ) )
        eType = DATATYPE_UINT32;
    else if( rStr.equalsAscii( "int64" ) )
        eType = DATATYPE_INT64;
    else if( rStr.equalsAscii( "uint64" ) )
        eType = DATATYPE_UINT64;
    else if( rStr.equalsAscii( "float" ) )
        eType = DATATYPE_FLOAT;
    else if( rStr.equalsAscii( "double" ) )
        eType = DATATYPE_DOUBLE;
    return eType;
}

FormatType StringHelper::convertToFormatType( const OUString& rStr )
{
    FormatType eType = FORMATTYPE_NONE;
    if( rStr.equalsAscii( "dec" ) )
        eType = FORMATTYPE_DEC;
    else if( rStr.equalsAscii( "hex" ) )
        eType = FORMATTYPE_HEX;
    else if( rStr.equalsAscii( "shorthex" ) )
        eType = FORMATTYPE_SHORTHEX;
    else if( rStr.equalsAscii( "bin" ) )
        eType = FORMATTYPE_BIN;
    else if( rStr.equalsAscii( "fix" ) )
        eType = FORMATTYPE_FIX;
    else if( rStr.equalsAscii( "bool" ) )
        eType = FORMATTYPE_BOOL;
    return eType;
}

bool StringHelper::convertFromDec( sal_Int64& ornData, const OUString& rData )
{
    sal_Int32 nPos = 0;
    sal_Int32 nLen = rData.getLength();
    bool bNeg = false;
    if( (nLen > 0) && (rData[ 0 ] == '-') )
    {
        bNeg = true;
        ++nPos;
    }
    ornData = 0;
    for( ; nPos < nLen; ++nPos )
    {
        sal_Unicode cChar = rData[ nPos ];
        if( (cChar < '0') || (cChar > '9') )
            return false;
        (ornData *= 10) += (cChar - '0');
    }
    if( bNeg )
        ornData *= -1;
    return true;
}

bool StringHelper::convertFromHex( sal_Int64& ornData, const OUString& rData )
{
    ornData = 0;
    for( sal_Int32 nPos = 0, nLen = rData.getLength(); nPos < nLen; ++nPos )
    {
        sal_Unicode cChar = rData[ nPos ];
        if( ('0' <= cChar) && (cChar <= '9') )
            cChar -= '0';
        else if( ('A' <= cChar) && (cChar <= 'F') )
            cChar -= ('A' - 10);
        else if( ('a' <= cChar) && (cChar <= 'f') )
            cChar -= ('a' - 10);
        else
            return false;
        (ornData <<= 4) += cChar;
    }
    return true;
}

bool StringHelper::convertStringToInt( sal_Int64& ornData, const OUString& rData )
{
    if( (rData.getLength() > 2) && (rData[ 0 ] == '0') && ((rData[ 1 ] == 'X') || (rData[ 1 ] == 'x')) )
        return convertFromHex( ornData, rData.copy( 2 ) );
    return convertFromDec( ornData, rData );
}

bool StringHelper::convertStringToDouble( double& orfData, const OUString& rData )
{
    rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
    sal_Int32 nSize = 0;
    orfData = rtl::math::stringToDouble( rData, '.', '\0', &eStatus, &nSize );
    return (eStatus == rtl_math_ConversionStatus_Ok) && (nSize == rData.getLength());
}

bool StringHelper::convertStringToBool( const OUString& rData )
{
    if( rData.equalsAscii( "true" ) )
        return true;
    if( rData.equalsAscii( "false" ) )
        return false;
    sal_Int64 nData;
    return convertStringToInt( nData, rData ) && (nData != 0);
}

OUStringPair StringHelper::convertStringToPair( const OUString& rString, sal_Unicode cSep )
{
    OUStringPair aPair;
    if( rString.getLength() > 0 )
    {
        sal_Int32 nEqPos = rString.indexOf( cSep );
        if( nEqPos < 0 )
        {
            aPair.first = rString;
        }
        else
        {
            aPair.first = StringHelper::trimSpaces( rString.copy( 0, nEqPos ) );
            aPair.second = StringHelper::trimSpaces( rString.copy( nEqPos + 1 ) );
        }
    }
    return aPair;
}

void StringHelper::convertStringToStringList( OUStringVector& orVec, const OUString& rData, bool bIgnoreEmpty )
{
    orVec.clear();
    OUString aUnquotedData = lclTrimQuotedStringList( rData );
    sal_Int32 nPos = 0;
    sal_Int32 nLen = aUnquotedData.getLength();
    while( (0 <= nPos) && (nPos < nLen) )
    {
        OUString aToken = getToken( aUnquotedData, nPos, OOX_DUMP_LF );
        if( !bIgnoreEmpty || (aToken.getLength() > 0) )
            orVec.push_back( aToken );
    }
}

void StringHelper::convertStringToIntList( Int64Vector& orVec, const OUString& rData, bool bIgnoreEmpty )
{
    orVec.clear();
    OUString aUnquotedData = lclTrimQuotedStringList( rData );
    sal_Int32 nPos = 0;
    sal_Int32 nLen = aUnquotedData.getLength();
    sal_Int64 nData;
    while( (0 <= nPos) && (nPos < nLen) )
    {
        bool bOk = convertStringToInt( nData, getToken( aUnquotedData, nPos, OOX_DUMP_LF ) );
        if( !bIgnoreEmpty || bOk )
            orVec.push_back( bOk ? nData : 0 );
    }
}

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

FormulaStack::FormulaStack() :
    mbError( false )
{
}

void FormulaStack::pushOperand( const String& rOp, const OUString& rTokClass )
{
    maFmlaStack.push( rOp );
    maClassStack.push( rTokClass );
}

void FormulaStack::pushOperand( const String& rOp )
{
    pushOperand( rOp, OUString( OOX_DUMP_BASECLASS ) );
}

void FormulaStack::pushUnaryOp( const String& rLOp, const String& rROp )
{
    pushUnaryOp( maFmlaStack, rLOp, rROp );
    pushUnaryOp( maClassStack, rLOp, rROp );
}

void FormulaStack::pushBinaryOp( const String& rOp )
{
    pushBinaryOp( maFmlaStack, rOp );
    pushBinaryOp( maClassStack, rOp );
}

void FormulaStack::pushFuncOp( const String& rFunc, const OUString& rTokClass, sal_uInt8 nParamCount )
{
    pushFuncOp( maFmlaStack, rFunc, nParamCount );
    pushFuncOp( maClassStack, rTokClass, nParamCount );
}

void FormulaStack::replaceOnTop( const OUString& rOld, const OUString& rNew )
{
    if( !maFmlaStack.empty() )
    {
        sal_Int32 nPos = maFmlaStack.top().indexOf( rOld );
        if( nPos >= 0 )
            maFmlaStack.top() = maFmlaStack.top().copy( 0, nPos ) + rNew + maFmlaStack.top().copy( nPos + rOld.getLength() );
    }
}

const OUString& FormulaStack::getString( const StringStack& rStack ) const
{
    static const OUString saStackError = OOX_DUMP_ERRSTRING( "stack" );
    return (mbError || rStack.empty()) ? saStackError : rStack.top();
}

void FormulaStack::pushUnaryOp( StringStack& rStack, const OUString& rLOp, const OUString& rROp )
{
    if( check( !rStack.empty() ) )
        rStack.top() = rLOp + rStack.top() + rROp;
}

void FormulaStack::pushBinaryOp( StringStack& rStack, const OUString& rOp )
{
    OUString aSecond;
    if( check( !rStack.empty() ) )
    {
        aSecond = rStack.top();
        rStack.pop();
    }
    if( check( !rStack.empty() ) )
        rStack.top() = rStack.top() + rOp + aSecond;
}

void FormulaStack::pushFuncOp( StringStack& rStack, const OUString& rOp, sal_uInt8 nParamCount )
{
    OUStringBuffer aFunc;
    for( sal_uInt8 nParam = 0; (nParam < nParamCount) && check( !rStack.empty() ); ++nParam )
    {
        StringHelper::prependToken( aFunc, rStack.top(), OOX_DUMP_FUNCSEP );
        rStack.pop();
    }
    StringHelper::enclose( aFunc, '(', ')' );
    aFunc.insert( 0, rOp );
    rStack.push( aFunc.makeStringAndClear() );
}

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

Base::~Base()
{
}

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

ConfigItemBase::~ConfigItemBase()
{
}

void ConfigItemBase::readConfigBlock( TextInputStream& rStrm )
{
    readConfigBlockContents( rStrm );
}

void ConfigItemBase::implProcessConfigItemStr(
        TextInputStream& /*rStrm*/, const OUString& /*rKey*/, const OUString& /*rData*/ )
{
}

void ConfigItemBase::implProcessConfigItemInt(
        TextInputStream& /*rStrm*/, sal_Int64 /*nKey*/, const OUString& /*rData*/ )
{
}

void ConfigItemBase::readConfigBlockContents( TextInputStream& rStrm )
{
    bool bLoop = true;
    while( bLoop && !rStrm.isEof() )
    {
        OUString aKey, aData;
        switch( readConfigLine( rStrm, aKey, aData ) )
        {
            case LINETYPE_DATA:
                processConfigItem( rStrm, aKey, aData );
            break;
            case LINETYPE_END:
                bLoop = false;
            break;
        }
    }
}

ConfigItemBase::LineType ConfigItemBase::readConfigLine(
        TextInputStream& rStrm, OUString& orKey, OUString& orData ) const
{
    OUString aLine;
    while( !rStrm.isEof() && (aLine.getLength() == 0) )
    {
        aLine = rStrm.readLine();
        if( (aLine.getLength() > 0) && (aLine[ 0 ] == OOX_DUMP_BOM) )
            aLine = aLine.copy( 1 );
        aLine = StringHelper::trimSpaces( aLine );
        if( aLine.getLength() > 0 )
        {
            // ignore comments (starting with hash or semicolon)
            sal_Unicode cChar = aLine[ 0 ];
            if( (cChar == '#') || (cChar == ';') )
                aLine = OUString();
        }
    }

    OUStringPair aPair = StringHelper::convertStringToPair( aLine );
    orKey = aPair.first;
    orData = aPair.second;
    return ((orKey.getLength() > 0) && ((orData.getLength() > 0) || !orKey.equalsAscii( "end" ))) ?
        LINETYPE_DATA : LINETYPE_END;
}

ConfigItemBase::LineType ConfigItemBase::readConfigLine( TextInputStream& rStrm ) const
{
    OUString aKey, aData;
    return readConfigLine( rStrm, aKey, aData );
}

void ConfigItemBase::processConfigItem(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    sal_Int64 nKey;
    if( StringHelper::convertStringToInt( nKey, rKey ) )
        implProcessConfigItemInt( rStrm, nKey, rData );
    else
        implProcessConfigItemStr( rStrm, rKey, rData );
}

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

NameListBase::~NameListBase()
{
}

void NameListBase::setName( sal_Int64 nKey, const String& rName )
{
    implSetName( nKey, rName );
}

void NameListBase::includeList( const NameListRef& rxList )
{
    if( rxList.get() )
    {
        for( const_iterator aIt = rxList->begin(), aEnd = rxList->end(); aIt != aEnd; ++aIt )
            maMap[ aIt->first ] = aIt->second;
        implIncludeList( *rxList );
    }
}

bool NameListBase::implIsValid() const
{
    return true;
}

void NameListBase::implProcessConfigItemStr(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    if( rKey.equalsAscii( "include" ) )
        include( rData );
    else if( rKey.equalsAscii( "exclude" ) )
        exclude( rData );
    else
        ConfigItemBase::implProcessConfigItemStr( rStrm, rKey, rData );
}

void NameListBase::implProcessConfigItemInt(
        TextInputStream& /*rStrm*/, sal_Int64 nKey, const OUString& rData )
{
    implSetName( nKey, rData );
}

void NameListBase::insertRawName( sal_Int64 nKey, const OUString& rName )
{
    maMap[ nKey ] = rName;
}

const OUString* NameListBase::findRawName( sal_Int64 nKey ) const
{
    const_iterator aIt = maMap.find( nKey );
    return (aIt == end()) ? 0 : &aIt->second;
}

void NameListBase::include( const OUString& rListKeys )
{
    OUStringVector aVec;
    StringHelper::convertStringToStringList( aVec, rListKeys, true );
    for( OUStringVector::const_iterator aIt = aVec.begin(), aEnd = aVec.end(); aIt != aEnd; ++aIt )
        includeList( mrCfgData.getNameList( *aIt ) );
}

void NameListBase::exclude( const OUString& rKeys )
{
    Int64Vector aVec;
    StringHelper::convertStringToIntList( aVec, rKeys, true );
    for( Int64Vector::const_iterator aIt = aVec.begin(), aEnd = aVec.end(); aIt != aEnd; ++aIt )
        maMap.erase( *aIt );
}

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

void ItemFormatMap::insertFormats( const NameListRef& rxNameList )
{
    if( Base::isValid( rxNameList ) )
        for( NameListBase::const_iterator aIt = rxNameList->begin(), aEnd = rxNameList->end(); aIt != aEnd; ++aIt )
            (*this)[ aIt->first ].parse( aIt->second );
}

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

ConstList::ConstList( const SharedConfigData& rCfgData ) :
    NameListBase( rCfgData ),
    maDefName( OOX_DUMP_ERR_NONAME ),
    mbQuoteNames( false )
{
}

void ConstList::implProcessConfigItemStr(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    if( rKey.equalsAscii( "default" ) )
        setDefaultName( rData );
    else if( rKey.equalsAscii( "quote-names" ) )
        setQuoteNames( StringHelper::convertStringToBool( rData ) );
    else
        NameListBase::implProcessConfigItemStr( rStrm, rKey, rData );
}

void ConstList::implSetName( sal_Int64 nKey, const OUString& rName )
{
    insertRawName( nKey, rName );
}

OUString ConstList::implGetName( const Config& /*rCfg*/, sal_Int64 nKey ) const
{
    const OUString* pName = findRawName( nKey );
    OUString aName = pName ? *pName : maDefName;
    if( mbQuoteNames )
    {
        OUStringBuffer aBuffer( aName );
        StringHelper::enclose( aBuffer, OOX_DUMP_STRQUOTE );
        aName = aBuffer.makeStringAndClear();
    }
    return aName;
}

OUString ConstList::implGetNameDbl( const Config& /*rCfg*/, double /*fValue*/ ) const
{
    return OUString();
}

void ConstList::implIncludeList( const NameListBase& rList )
{
    if( const ConstList* pConstList = dynamic_cast< const ConstList* >( &rList ) )
    {
        maDefName = pConstList->maDefName;
        mbQuoteNames = pConstList->mbQuoteNames;
    }
}

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

MultiList::MultiList( const SharedConfigData& rCfgData ) :
    ConstList( rCfgData ),
    mbIgnoreEmpty( true )
{
}

void MultiList::setNamesFromVec( sal_Int64 nStartKey, const OUStringVector& rNames )
{
    sal_Int64 nKey = nStartKey;
    for( OUStringVector::const_iterator aIt = rNames.begin(), aEnd = rNames.end(); aIt != aEnd; ++aIt, ++nKey )
        if( !mbIgnoreEmpty || (aIt->getLength() > 0) )
            insertRawName( nKey, *aIt );
}

void MultiList::implProcessConfigItemStr(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    if( rKey.equalsAscii( "ignore-empty" ) )
        mbIgnoreEmpty = StringHelper::convertStringToBool( rData );
    else
        ConstList::implProcessConfigItemStr( rStrm, rKey, rData );
}

void MultiList::implSetName( sal_Int64 nKey, const OUString& rName )
{
    OUStringVector aNames;
    StringHelper::convertStringToStringList( aNames, rName, false );
    setNamesFromVec( nKey, aNames );
}

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

FlagsList::FlagsList( const SharedConfigData& rCfgData ) :
    NameListBase( rCfgData ),
    mnIgnore( 0 )
{
}

void FlagsList::implProcessConfigItemStr(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    if( rKey.equalsAscii( "ignore" ) )
    {
        sal_Int64 nIgnore;
        if( StringHelper::convertStringToInt( nIgnore, rData ) )
            setIgnoreFlags( nIgnore );
    }
    else
    {
        NameListBase::implProcessConfigItemStr( rStrm, rKey, rData );
    }
}

void FlagsList::implSetName( sal_Int64 nKey, const OUString& rName )
{
    if( (nKey != 0) && ((nKey & (nKey - 1)) == 0) )  // only a single bit set?
        insertRawName( nKey, rName );
}

OUString FlagsList::implGetName( const Config& /*rCfg*/, sal_Int64 nKey ) const
{
    sal_Int64 nFound = mnIgnore;
    OUStringBuffer aName;
    // add known flags
    for( const_iterator aIt = begin(), aEnd = end(); aIt != aEnd; ++aIt )
    {
        sal_Int64 nMask = aIt->first;
        setFlag( nFound, nMask );
        if( !getFlag( mnIgnore, nMask ) )
        {
            const OUString& rFlagName = aIt->second;
            bool bOnOff = (rFlagName.getLength() > 0) && (rFlagName[ 0 ] == ':');
            bool bFlag = getFlag( nKey, nMask );
            if( bOnOff )
            {
                StringHelper::appendToken( aName, rFlagName.copy( 1 ) );
                aName.appendAscii( bFlag ? ":on" : ":off" );
            }
            else
            {
                bool bNegated = (rFlagName.getLength() > 0) && (rFlagName[ 0 ] == '!');
                sal_Int32 nBothSep = bNegated ? rFlagName.indexOf( '!', 1 ) : -1;
                if( bFlag )
                {
                    if( !bNegated )
                        StringHelper::appendToken( aName, rFlagName );
                    else if( nBothSep > 0 )
                        StringHelper::appendToken( aName, rFlagName.copy( nBothSep + 1 ) );
                }
                else if( bNegated )
                {
                    if( nBothSep > 0 )
                        StringHelper::appendToken( aName, rFlagName.copy( 1, nBothSep - 1 ) );
                    else
                        StringHelper::appendToken( aName, rFlagName.copy( 1 ) );
                }
            }
        }
    }
    // add unknown flags
    setFlag( nKey, nFound, false );
    if( nKey != 0 )
    {
        OUStringBuffer aUnknown( CREATE_OUSTRING( OOX_DUMP_UNKNOWN ) );
        aUnknown.append( OOX_DUMP_ITEMSEP );
        StringHelper::appendShortHex( aUnknown, nKey, true );
        StringHelper::enclose( aUnknown, '(', ')' );
        StringHelper::appendToken( aName, aUnknown.makeStringAndClear() );
    }
    return aName.makeStringAndClear();
}

OUString FlagsList::implGetNameDbl( const Config& /*rCfg*/, double /*fValue*/ ) const
{
    return OUString();
}

void FlagsList::implIncludeList( const NameListBase& rList )
{
    if( const FlagsList* pFlagsList = dynamic_cast< const FlagsList* >( &rList ) )
        mnIgnore = pFlagsList->mnIgnore;
}

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

bool CombiList::ExtItemFormatKey::operator<( const ExtItemFormatKey& rRight ) const
{
    return (mnKey < rRight.mnKey) || ((mnKey == rRight.mnKey) && (maFilter < rRight.maFilter));
}

CombiList::CombiList( const SharedConfigData& rCfgData ) :
    FlagsList( rCfgData )
{
}

void CombiList::implSetName( sal_Int64 nKey, const OUString& rName )
{
    if( (nKey & (nKey - 1)) != 0 )  // more than a single bit set?
    {
        typedef ::std::set< ExtItemFormatKey > ExtItemFormatKeySet;
        ::std::set< ExtItemFormatKey > aItemKeys;
        ExtItemFormat aItemFmt;
        OUStringVector aRemain = aItemFmt.parse( rName );
        for( OUStringVector::iterator aIt = aRemain.begin(), aEnd = aRemain.end(); aIt != aEnd; ++aIt )
        {
            OUStringPair aPair = StringHelper::convertStringToPair( *aIt );
            if( aPair.first.equalsAscii( "noshift" ) )
            {
                aItemFmt.mbShiftValue = StringHelper::convertStringToBool( aPair.second );
            }
            else if( aPair.first.equalsAscii( "filter" ) )
            {
                OUStringPair aFilter = StringHelper::convertStringToPair( aPair.second, '~' );
                ExtItemFormatKey aKey( nKey );
                if( (aFilter.first.getLength() > 0) && StringHelper::convertStringToInt( aKey.maFilter.first, aFilter.first ) &&
                    (aFilter.second.getLength() > 0) && StringHelper::convertStringToInt( aKey.maFilter.second, aFilter.second ) )
                {
                    if( aKey.maFilter.first == 0 )
                        aKey.maFilter.second = 0;
                    aItemKeys.insert( aKey );
                }
            }
        }
        if( aItemKeys.empty() )
            aItemKeys.insert( ExtItemFormatKey( nKey ) );
        for( ExtItemFormatKeySet::iterator aIt = aItemKeys.begin(), aEnd = aItemKeys.end(); aIt != aEnd; ++aIt )
            maFmtMap[ *aIt ] = aItemFmt;
    }
    else
    {
        FlagsList::implSetName( nKey, rName );
    }
}

OUString CombiList::implGetName( const Config& rCfg, sal_Int64 nKey ) const
{
    sal_Int64 nFound = 0;
    OUStringBuffer aName;
    // add known flag fields
    for( ExtItemFormatMap::const_iterator aIt = maFmtMap.begin(), aEnd = maFmtMap.end(); aIt != aEnd; ++aIt )
    {
        const ExtItemFormatKey& rMapKey = aIt->first;
        sal_Int64 nMask = rMapKey.mnKey;
        if( (nMask != 0) && ((nKey & rMapKey.maFilter.first) == rMapKey.maFilter.second) )
        {
            const ExtItemFormat& rItemFmt = aIt->second;

            sal_uInt64 nUFlags = static_cast< sal_uInt64 >( nKey );
            sal_uInt64 nUMask = static_cast< sal_uInt64 >( nMask );
            if( rItemFmt.mbShiftValue )
                while( (nUMask & 1) == 0 ) { nUFlags >>= 1; nUMask >>= 1; }

            sal_uInt64 nUValue = nUFlags & nUMask;
            sal_Int64 nSValue = static_cast< sal_Int64 >( nUValue );
            if( getFlag< sal_uInt64 >( nUValue, (nUMask + 1) >> 1 ) )
                setFlag( nSValue, static_cast< sal_Int64 >( ~nUMask ) );

            OUStringBuffer aItem( rItemFmt.maItemName );
            OUStringBuffer aValue;
            switch( rItemFmt.meDataType )
            {
                case DATATYPE_INT8:     StringHelper::appendValue( aValue, static_cast< sal_Int8 >( nSValue ), rItemFmt.meFmtType );    break;
                case DATATYPE_UINT8:    StringHelper::appendValue( aValue, static_cast< sal_uInt8 >( nUValue ), rItemFmt.meFmtType );   break;
                case DATATYPE_INT16:    StringHelper::appendValue( aValue, static_cast< sal_Int16 >( nSValue ), rItemFmt.meFmtType );   break;
                case DATATYPE_UINT16:   StringHelper::appendValue( aValue, static_cast< sal_uInt16 >( nUValue ), rItemFmt.meFmtType );  break;
                case DATATYPE_INT32:    StringHelper::appendValue( aValue, static_cast< sal_Int32 >( nSValue ), rItemFmt.meFmtType );   break;
                case DATATYPE_UINT32:   StringHelper::appendValue( aValue, static_cast< sal_uInt32 >( nUValue ), rItemFmt.meFmtType );  break;
                case DATATYPE_INT64:    StringHelper::appendValue( aValue, nSValue, rItemFmt.meFmtType );                               break;
                case DATATYPE_UINT64:   StringHelper::appendValue( aValue, nUValue, rItemFmt.meFmtType );                               break;
                case DATATYPE_FLOAT:    StringHelper::appendValue( aValue, static_cast< float >( nSValue ), rItemFmt.meFmtType );       break;
                case DATATYPE_DOUBLE:   StringHelper::appendValue( aValue, static_cast< double >( nSValue ), rItemFmt.meFmtType );      break;
                default:;
            }
            StringHelper::appendToken( aItem, aValue.makeStringAndClear(), OOX_DUMP_ITEMSEP );
            if( rItemFmt.maListName.getLength() > 0 )
            {
                OUString aValueName = rCfg.getName( rItemFmt.maListName, static_cast< sal_Int64 >( nUValue ) );
                StringHelper::appendToken( aItem, aValueName, OOX_DUMP_ITEMSEP );
            }
            StringHelper::enclose( aItem, '(', ')' );
            StringHelper::appendToken( aName, aItem.makeStringAndClear() );
            setFlag( nFound, nMask );
        }
    }
    setFlag( nKey, nFound, false );
    StringHelper::appendToken( aName, FlagsList::implGetName( rCfg, nKey ) );
    return aName.makeStringAndClear();
}

void CombiList::implIncludeList( const NameListBase& rList )
{
    if( const CombiList* pCombiList = dynamic_cast< const CombiList* >( &rList ) )
        maFmtMap = pCombiList->maFmtMap;
    FlagsList::implIncludeList( rList );
}

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

UnitConverter::UnitConverter( const SharedConfigData& rCfgData ) :
    NameListBase( rCfgData ),
    mfFactor( 1.0 )
{
}

void UnitConverter::implSetName( sal_Int64 /*nKey*/, const OUString& /*rName*/ )
{
    // nothing to do
}

OUString UnitConverter::implGetName( const Config& rCfg, sal_Int64 nKey ) const
{
    return implGetNameDbl( rCfg, static_cast< double >( nKey ) );
}

OUString UnitConverter::implGetNameDbl( const Config& /*rCfg*/, double fValue ) const
{
    OUStringBuffer aValue;
    StringHelper::appendDec( aValue, mfFactor * fValue );
    aValue.append( maUnitName );
    return aValue.makeStringAndClear();
}

void UnitConverter::implIncludeList( const NameListBase& /*rList*/ )
{
}

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

NameListRef NameListWrapper::getNameList( const Config& rCfg ) const
{
    return mxList.get() ? mxList : (mxList = rCfg.getNameList( maName ));
}

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

SharedConfigData::SharedConfigData( const OUString& rFileName,
        const Reference< XMultiServiceFactory >& rxFactory, const StorageRef& rxRootStrg,
        const OUString& rSysFileName, MediaDescriptor& rMediaDesc ) :
    mxFactory( rxFactory ),
    mxRootStrg( rxRootStrg ),
    maSysFileName( rSysFileName ),
    mrMediaDesc( rMediaDesc ),
    mbLoaded( false ),
    mbPwCancelled( false )
{
    OUString aFileUrl = InputOutputHelper::convertFileNameToUrl( rFileName );
    if( aFileUrl.getLength() > 0 )
    {
        sal_Int32 nNamePos = InputOutputHelper::getFileNamePos( aFileUrl );
        maConfigPath = aFileUrl.copy( 0, nNamePos );
        mbLoaded = readConfigFile( aFileUrl );
    }
}

SharedConfigData::~SharedConfigData()
{
}

void SharedConfigData::setOption( const OUString& rKey, const OUString& rData )
{
    maConfigData[ rKey ] = rData;
}

const OUString* SharedConfigData::getOption( const OUString& rKey ) const
{
    ConfigDataMap::const_iterator aIt = maConfigData.find( rKey );
    return (aIt == maConfigData.end()) ? 0 : &aIt->second;
}

void SharedConfigData::setNameList( const OUString& rListName, const NameListRef& rxList )
{
    if( rListName.getLength() > 0 )
        maNameLists[ rListName ] = rxList;
}

void SharedConfigData::eraseNameList( const OUString& rListName )
{
    maNameLists.erase( rListName );
}

NameListRef SharedConfigData::getNameList( const OUString& rListName ) const
{
    NameListRef xList;
    NameListMap::const_iterator aIt = maNameLists.find( rListName );
    if( aIt != maNameLists.end() )
        xList = aIt->second;
    return xList;
}

OUString SharedConfigData::requestPassword( ::comphelper::IDocPasswordVerifier& rVerifier )
{
    OUString aPassword;
    if( !mbPwCancelled )
    {
        ::std::vector< OUString > aDefaultPasswords;
        aDefaultPasswords.push_back( CREATE_OUSTRING( "VelvetSweatshop" ) );
        aPassword = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
            rVerifier, mrMediaDesc, ::comphelper::DocPasswordRequestType_MS, &aDefaultPasswords );
        mbPwCancelled = aPassword.getLength() == 0;
    }
    return aPassword;
}

bool SharedConfigData::implIsValid() const
{
    return mbLoaded && mxFactory.is() && mxRootStrg.get() && (maSysFileName.getLength() > 0);
}

void SharedConfigData::implProcessConfigItemStr(
        TextInputStream& rStrm, const OUString& rKey, const OUString& rData )
{
    if( rKey.equalsAscii( "include-config-file" ) )
        readConfigFile( maConfigPath + rData );
    else if( rKey.equalsAscii( "constlist" ) )
        readNameList< ConstList >( rStrm, rData );
    else if( rKey.equalsAscii( "multilist" ) )
        readNameList< MultiList >( rStrm, rData );
    else if( rKey.equalsAscii( "flagslist" ) )
        readNameList< FlagsList >( rStrm, rData );
    else if( rKey.equalsAscii( "combilist" ) )
        readNameList< CombiList >( rStrm, rData );
    else if( rKey.equalsAscii( "shortlist" ) )
        createShortList( rData );
    else if( rKey.equalsAscii( "unitconverter" ) )
        createUnitConverter( rData );
    else
        setOption( rKey, rData );
}

bool SharedConfigData::readConfigFile( const OUString& rFileUrl )
{
    bool bLoaded = maConfigFiles.count( rFileUrl ) > 0;
    if( !bLoaded )
    {
        Reference< XInputStream > xInStrm = InputOutputHelper::openInputStream( mxFactory, rFileUrl );
        BinaryXInputStream aInStrm( xInStrm, true );
        TextInputStream aTxtStrm( aInStrm, RTL_TEXTENCODING_UTF8 );
        if( !aTxtStrm.isEof() )
        {
            maConfigFiles.insert( rFileUrl );
            readConfigBlockContents( aTxtStrm );
            bLoaded = true;
        }
    }
    return bLoaded;
}

void SharedConfigData::createShortList( const OUString& rData )
{
    OUStringVector aDataVec;
    StringHelper::convertStringToStringList( aDataVec, rData, false );
    if( aDataVec.size() >= 3 )
    {
        sal_Int64 nStartKey;
        if( StringHelper::convertStringToInt( nStartKey, aDataVec[ 1 ] ) )
        {
            ::boost::shared_ptr< MultiList > xList = createNameList< MultiList >( aDataVec[ 0 ] );
            if( xList.get() )
            {
                aDataVec.erase( aDataVec.begin(), aDataVec.begin() + 2 );
                xList->setNamesFromVec( nStartKey, aDataVec );
            }
        }
    }
}

void SharedConfigData::createUnitConverter( const OUString& rData )
{
    OUStringVector aDataVec;
    StringHelper::convertStringToStringList( aDataVec, rData, false );
    if( aDataVec.size() >= 2 )
    {
        OUString aFactor = aDataVec[ 1 ];
        bool bRecip = (aFactor.getLength() > 0) && (aFactor[ 0 ] == '/');
        if( bRecip )
            aFactor = aFactor.copy( 1 );
        double fFactor;
        if( StringHelper::convertStringToDouble( fFactor, aFactor ) && (fFactor != 0.0) )
        {
            ::boost::shared_ptr< UnitConverter > xList = createNameList< UnitConverter >( aDataVec[ 0 ] );
            if( xList.get() )
            {
                xList->setFactor( bRecip ? (1.0 / fFactor) : fFactor );
                if( aDataVec.size() >= 3 )
                    xList->setUnitName( aDataVec[ 2 ] );
            }
        }
    }
}

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

Config::Config( const Config& rParent ) :
    Base()  // c'tor needs to be called explicitly to avoid compiler warning
{
    construct( rParent );
}

Config::Config( const sal_Char* pcEnvVar, const FilterBase& rFilter )
{
    construct( pcEnvVar, rFilter );
}

Config::Config( const sal_Char* pcEnvVar, const Reference< XMultiServiceFactory >& rxFactory, const StorageRef& rxRootStrg, const OUString& rSysFileName, MediaDescriptor& rMediaDesc )
{
    construct( pcEnvVar, rxFactory, rxRootStrg, rSysFileName, rMediaDesc );
}

Config::~Config()
{
}

void Config::construct( const Config& rParent )
{
    *this = rParent;
}

void Config::construct( const sal_Char* pcEnvVar, const FilterBase& rFilter )
{
    if( rFilter.getFileUrl().getLength() > 0 )
        construct( pcEnvVar, rFilter.getGlobalFactory(), rFilter.getStorage(), rFilter.getFileUrl(), rFilter.getMediaDescriptor() );
}

void Config::construct( const sal_Char* pcEnvVar, const Reference< XMultiServiceFactory >& rxFactory, const StorageRef& rxRootStrg, const OUString& rSysFileName, MediaDescriptor& rMediaDesc )
{
    if( pcEnvVar && rxRootStrg.get() && (rSysFileName.getLength() > 0) )
        if( const sal_Char* pcFileName = ::getenv( pcEnvVar ) )
            mxCfgData.reset( new SharedConfigData( OUString::createFromAscii( pcFileName ), rxFactory, rxRootStrg, rSysFileName, rMediaDesc ) );
}

void Config::setStringOption( const String& rKey, const String& rData )
{
    mxCfgData->setOption( rKey, rData );
}

const OUString& Config::getStringOption( const String& rKey, const OUString& rDefault ) const
{
    const OUString* pData = implGetOption( rKey );
    return pData ? *pData : rDefault;
}

bool Config::getBoolOption( const String& rKey, bool bDefault ) const
{
    const OUString* pData = implGetOption( rKey );
    return pData ? StringHelper::convertStringToBool( *pData ) : bDefault;
}

bool Config::isDumperEnabled() const
{
    return getBoolOption( "enable-dumper", false );
}

bool Config::isImportEnabled() const
{
    return getBoolOption( "enable-import", true );
}

void Config::setNameList( const String& rListName, const NameListRef& rxList )
{
    mxCfgData->setNameList( rListName, rxList );
}

void Config::eraseNameList( const String& rListName )
{
    mxCfgData->eraseNameList( rListName );
}

NameListRef Config::getNameList( const String& rListName ) const
{
    return implGetNameList( rListName );
}

OUString Config::requestPassword( ::comphelper::IDocPasswordVerifier& rVerifier )
{
    return mxCfgData->requestPassword( rVerifier );
}

bool Config::isPasswordCancelled() const
{
    return mxCfgData->isPasswordCancelled();
}

bool Config::implIsValid() const
{
    return isValid( mxCfgData );
}

const OUString* Config::implGetOption( const OUString& rKey ) const
{
    return mxCfgData->getOption( rKey );
}

NameListRef Config::implGetNameList( const OUString& rListName ) const
{
    return mxCfgData->getNameList( rListName );
}

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

Output::Output( const Reference< XTextOutputStream >& rxStrm )
{
    construct( rxStrm );
}

Output::Output( const Reference< XMultiServiceFactory >& rxFactory, const OUString& rFileName )
{
    construct( InputOutputHelper::openTextOutputStream( rxFactory, rFileName, CREATE_OUSTRING( "UTF-8" ) ) );
}

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

void Output::newLine()
{
    if( maLine.getLength() > 0 )
    {
        mxStrm->writeString( maIndent );
        maLine.append( sal_Unicode( '\n' ) );
        mxStrm->writeString( maLine.makeStringAndClear() );
        mnCol = 0;
        mnLastItem = 0;
    }
}

void Output::emptyLine( size_t nCount )
{
    for( size_t nIdx = 0; nIdx < nCount; ++nIdx )
        mxStrm->writeString( OUString( sal_Unicode( '\n' ) ) );
}

void Output::incIndent()
{
    OUStringBuffer aBuffer( maIndent );
    StringHelper::appendChar( aBuffer, ' ', OOX_DUMP_INDENT );
    maIndent = aBuffer.makeStringAndClear();
}

void Output::decIndent()
{
    if( maIndent.getLength() >= OOX_DUMP_INDENT )
        maIndent = maIndent.copy( OOX_DUMP_INDENT );
}

void Output::resetIndent()
{
    maIndent = OUString();
}

void Output::startTable( sal_Int32 nW1 )
{
    startTable( 1, &nW1 );
}

void Output::startTable( sal_Int32 nW1, sal_Int32 nW2 )
{
    sal_Int32 pnColWidths[ 2 ];
    pnColWidths[ 0 ] = nW1;
    pnColWidths[ 1 ] = nW2;
    startTable( 2, pnColWidths );
}

void Output::startTable( sal_Int32 nW1, sal_Int32 nW2, sal_Int32 nW3 )
{
    sal_Int32 pnColWidths[ 3 ];
    pnColWidths[ 0 ] = nW1;
    pnColWidths[ 1 ] = nW2;
    pnColWidths[ 2 ] = nW3;
    startTable( 3, pnColWidths );
}

void Output::startTable( sal_Int32 nW1, sal_Int32 nW2, sal_Int32 nW3, sal_Int32 nW4 )
{
    sal_Int32 pnColWidths[ 4 ];
    pnColWidths[ 0 ] = nW1;
    pnColWidths[ 1 ] = nW2;
    pnColWidths[ 2 ] = nW3;
    pnColWidths[ 3 ] = nW4;
    startTable( 4, pnColWidths );
}

void Output::startTable( size_t nColCount, const sal_Int32* pnColWidths )
{
    maColPos.clear();
    maColPos.push_back( 0 );
    sal_Int32 nColPos = 0;
    for( size_t nCol = 0; nCol < nColCount; ++nCol )
    {
        nColPos = nColPos + pnColWidths[ nCol ];
        maColPos.push_back( nColPos );
    }
}

void Output::tab()
{
    tab( mnCol + 1 );
}

void Output::tab( size_t nCol )
{
    mnCol = nCol;
    if( mnCol < maColPos.size() )
    {
        sal_Int32 nColPos = maColPos[ mnCol ];
        if( maLine.getLength() >= nColPos )
            maLine.setLength( ::std::max< sal_Int32 >( nColPos - 1, 0 ) );
        StringHelper::appendChar( maLine, ' ', nColPos - maLine.getLength() );
    }
    else
    {
        StringHelper::appendChar( maLine, ' ', 2 );
    }
}

void Output::endTable()
{
    maColPos.clear();
}

void Output::resetItemIndex( sal_Int64 nIdx )
{
    mnItemIdx = nIdx;
}

void Output::startItem( const String& rItemName )
{
    if( mnItemLevel == 0 )
    {
        if( (mnMultiLevel > 0) && (maLine.getLength() > 0) )
            tab();
        if( rItemName.has() )
        {
            writeItemName( rItemName );
            writeChar( OOX_DUMP_ITEMSEP );
        }
    }
    ++mnItemLevel;
    mnLastItem = maLine.getLength();
}

void Output::contItem()
{
    if( mnItemLevel > 0 )
    {
        if( (maLine.getLength() == 0) || (maLine[ maLine.getLength() - 1 ] != OOX_DUMP_ITEMSEP) )
            writeChar( OOX_DUMP_ITEMSEP );
        mnLastItem = maLine.getLength();
    }
}

void Output::endItem()
{
    if( mnItemLevel > 0 )
    {
        maLastItem = OUString( maLine.getStr() + mnLastItem );
        if( (maLastItem.getLength() == 0) && (mnLastItem > 0) && (maLine[ mnLastItem - 1 ] == OOX_DUMP_ITEMSEP) )
            maLine.setLength( mnLastItem - 1 );
        --mnItemLevel;
    }
    if( mnItemLevel == 0 )
    {
        if( mnMultiLevel == 0 )
            newLine();
    }
    else
        contItem();
}

void Output::startMultiItems()
{
    ++mnMultiLevel;
}

void Output::endMultiItems()
{
    if( mnMultiLevel > 0 )
        --mnMultiLevel;
    if( mnMultiLevel == 0 )
        newLine();
}

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

void Output::writeChar( sal_Unicode cChar, sal_Int32 nCount )
{
    StringHelper::appendEncChar( maLine, cChar, nCount );
}

void Output::writeAscii( const sal_Char* pcStr )
{
    if( pcStr )
        maLine.appendAscii( pcStr );
}

void Output::writeString( const OUString& rStr )
{
    StringHelper::appendEncString( maLine, rStr );
}

void Output::writeArray( const sal_uInt8* pnData, sal_Size nSize, sal_Unicode cSep )
{
    const sal_uInt8* pnEnd = pnData ? (pnData + nSize) : 0;
    for( const sal_uInt8* pnByte = pnData; pnByte < pnEnd; ++pnByte )
    {
        if( pnByte > pnData )
            writeChar( cSep );
        writeHex( *pnByte, false );
    }
}

void Output::writeBool( bool bData )
{
    StringHelper::appendBool( maLine, bData );
}

void Output::writeColorABGR( sal_Int32 nColor )
{
    writeChar( 'a' );
    writeDec( static_cast< sal_uInt8 >( nColor >> 24 ) );
    writeAscii( ",r" );
    writeDec( static_cast< sal_uInt8 >( nColor ) );
    writeAscii( ",g" );
    writeDec( static_cast< sal_uInt8 >( nColor >> 8 ) );
    writeAscii( ",b" );
    writeDec( static_cast< sal_uInt8 >( nColor >> 16 ) );
}

void Output::writeDateTime( const DateTime& rDateTime )
{
    writeDec( rDateTime.Year, 4, '0' );
    writeChar( '-' );
    writeDec( rDateTime.Month, 2, '0' );
    writeChar( '-' );
    writeDec( rDateTime.Day, 2, '0' );
    writeChar( 'T' );
    writeDec( rDateTime.Hours, 2, '0' );
    writeChar( ':' );
    writeDec( rDateTime.Minutes, 2, '0' );
    writeChar( ':' );
    writeDec( rDateTime.Seconds, 2, '0' );
}

void Output::writeColIndex( sal_Int32 nCol )
{
    StringHelper::appendAddrCol( maLine, nCol, true );
}

void Output::writeRowIndex( sal_Int32 nRow )
{
    StringHelper::appendAddrRow( maLine, nRow, true );
}

void Output::writeColRowRange( sal_Int32 nColRow1, sal_Int32 nColRow2 )
{
    writeDec( nColRow1 );
    writeChar( OOX_DUMP_RANGESEP );
    writeDec( nColRow2 );
}

void Output::writeColRange( sal_Int32 nCol1, sal_Int32 nCol2 )
{
    writeColIndex( nCol1 );
    writeChar( OOX_DUMP_RANGESEP );
    writeColIndex( nCol2 );
}

void Output::writeRowRange( sal_Int32 nRow1, sal_Int32 nRow2 )
{
    writeRowIndex( nRow1 );
    writeChar( OOX_DUMP_RANGESEP );
    writeRowIndex( nRow2 );
}

void Output::writeAddress( const Address& rPos )
{
    StringHelper::appendAddress( maLine, rPos );
}

void Output::writeRange( const Range& rRange )
{
    StringHelper::appendRange( maLine, rRange );
}

void Output::writeRangeList( const RangeList& rRanges )
{
    StringHelper::appendRangeList( maLine, rRanges );
}

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

void Output::construct( const Reference< XTextOutputStream >& rxStrm )
{
    mxStrm = rxStrm;
    mnCol = mnItemLevel = mnMultiLevel = 0;
    mnItemIdx = 0;
    mnLastItem = 0;
    if( mxStrm.is() )
    {
        writeChar( OOX_DUMP_BOM );
        newLine();
    }
}

bool Output::implIsValid() const
{
    return mxStrm.is();
}

void Output::writeItemName( const String& rItemName )
{
    if( rItemName.has() && (rItemName[ 0 ] == '#') )
    {
        writeString( rItemName.copy( 1 ) );
        StringHelper::appendIndex( maLine, mnItemIdx++ );
    }
    else
        writeString( rItemName );
}

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

StorageIterator::StorageIterator( const StorageRef& rxStrg ) :
    mxStrg( rxStrg )
{
    if( mxStrg.get() )
        mxStrg->getElementNames( maNames );
    maIt = maNames.begin();
}

StorageIterator::~StorageIterator()
{
}

size_t StorageIterator::getElementCount() const
{
    return maNames.size();
}

StorageIterator& StorageIterator::operator++()
{
    if( maIt != maNames.end() )
        ++maIt;
    return *this;
}

OUString StorageIterator::getName() const
{
    OUString aName;
    if( maIt != maNames.end() )
        aName = *maIt;
    return aName;
}

bool StorageIterator::isStream() const
{
    return isValid() && mxStrg->openInputStream( *maIt ).is();
}

bool StorageIterator::isStorage() const
{
    if( !isValid() )
        return false;
    StorageRef xStrg = mxStrg->openSubStorage( *maIt, false );
    return xStrg.get() && xStrg->isStorage();
}

bool StorageIterator::implIsValid() const
{
    return mxStrg.get() && mxStrg->isStorage() && (maIt != maNames.end());
}

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

ObjectBase::~ObjectBase()
{
}

void ObjectBase::construct( const ConfigRef& rxConfig )
{
    mxConfig = rxConfig;
}

void ObjectBase::construct( const ObjectBase& rParent )
{
    *this = rParent;
}

void ObjectBase::dump()
{
    if( isValid() )
        implDump();
}

bool ObjectBase::implIsValid() const
{
    return isValid( mxConfig );
}

void ObjectBase::implDump()
{
}

void ObjectBase::reconstructConfig( const ConfigRef& rxConfig )
{
    if( isValid( rxConfig ) )
        mxConfig = rxConfig;
}

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

void StorageObjectBase::construct( const ObjectBase& rParent, const StorageRef& rxStrg, const OUString& rSysPath )
{
    ObjectBase::construct( rParent );
    mxStrg = rxStrg;
    maSysPath = rSysPath;
}

void StorageObjectBase::construct( const ObjectBase& rParent )
{
    ObjectBase::construct( rParent );
    if( ObjectBase::implIsValid() )
    {
        mxStrg = cfg().getRootStorage();
        maSysPath = cfg().getSysFileName();
    }
}

bool StorageObjectBase::implIsValid() const
{
    return mxStrg.get() && (maSysPath.getLength() > 0) && ObjectBase::implIsValid();
}

void StorageObjectBase::implDump()
{
    bool bIsStrg = mxStrg->isStorage();
    bool bIsRoot = mxStrg->isRootStorage();
    Reference< XInputStream > xBaseStrm;
    if( !bIsStrg )
        xBaseStrm = mxStrg->openInputStream( OUString() );

    OUString aSysOutPath = maSysPath;
    if( bIsRoot ) try
    {
        aSysOutPath += OOX_DUMP_DUMPEXT;
        Reference< XSimpleFileAccess > xFileAccess( getFactory()->createInstance( CREATE_OUSTRING( "com.sun.star.ucb.SimpleFileAccess" ) ), UNO_QUERY_THROW );
        xFileAccess->kill( aSysOutPath );
    }
    catch( Exception& )
    {
    }

    if( bIsStrg )
    {
        extractStorage( mxStrg, OUString(), aSysOutPath );
    }
    else if( xBaseStrm.is() )
    {
        BinaryInputStreamRef xInStrm( new BinaryXInputStream( xBaseStrm, false ) );
        xInStrm->seekToStart();
        implDumpBaseStream( xInStrm, aSysOutPath );
    }
}

void StorageObjectBase::implDumpStream( const BinaryInputStreamRef&, const OUString&, const OUString&, const OUString& )
{
}

void StorageObjectBase::implDumpStorage( const StorageRef& rxStrg, const OUString& rStrgPath, const OUString& rSysPath )
{
    extractStorage( rxStrg, rStrgPath, rSysPath );
}

void StorageObjectBase::implDumpBaseStream( const BinaryInputStreamRef&, const OUString& )
{
}

void StorageObjectBase::addPreferredStream( const String& rStrmName )
{
    if( rStrmName.has() )
        maPreferred.push_back( PreferredItem( rStrmName, false ) );
}

void StorageObjectBase::addPreferredStorage( const String& rStrgPath )
{
    if( rStrgPath.has() )
        maPreferred.push_back( PreferredItem( rStrgPath, true ) );
}

OUString StorageObjectBase::getSysFileName( const OUString& rStrmName, const OUString& rSysOutPath )
{
    // encode all characters < 0x20
    OUStringBuffer aBuffer;
    StringHelper::appendEncString( aBuffer, rStrmName, false );

    // replace all characters reserved in file system
    OUString aFileName = aBuffer.makeStringAndClear();
    static const sal_Unicode spcReserved[] = { '/', '\\', ':', '*', '?', '<', '>', '|' };
    for( const sal_Unicode* pcChar = spcReserved; pcChar < STATIC_ARRAY_END( spcReserved ); ++pcChar )
        aFileName = aFileName.replace( *pcChar, '_' );

    // build full path
    return rSysOutPath + OUString( sal_Unicode( '/' ) ) + aFileName;
}

void StorageObjectBase::extractStream( StorageBase& rStrg, const OUString& rStrgPath, const OUString& rStrmName, const OUString& rSysFileName )
{
    BinaryXInputStream aInStrm( rStrg.openInputStream( rStrmName ), true );
    if( !aInStrm.isEof() )
    {
        BinaryXOutputStream aOutStrm( InputOutputHelper::openOutputStream( getFactory(), rSysFileName ), true );
        if( !aOutStrm.isEof() )
            aInStrm.copyToStream( aOutStrm );
    }
    BinaryXInputStreamRef xDumpStrm( new BinaryXInputStream( InputOutputHelper::openInputStream( getFactory(), rSysFileName ), true ) );
    if( !xDumpStrm->isEof() )
        implDumpStream( xDumpStrm, rStrgPath, rStrmName, rSysFileName );
}

void StorageObjectBase::extractStorage( const StorageRef& rxStrg, const OUString& rStrgPath, const OUString& rSysPath )
{
    // create directory in file system
    ::osl::FileBase::RC eRes = ::osl::Directory::create( rSysPath );
    if( (eRes != ::osl::FileBase::E_None) && (eRes != ::osl::FileBase::E_EXIST) )
        return;

    // process preferred storages and streams in root storage first
    if( rStrgPath.getLength() == 0 )
        for( PreferredItemVector::iterator aIt = maPreferred.begin(), aEnd = maPreferred.end(); aIt != aEnd; ++aIt )
            extractItem( rxStrg, rStrgPath, aIt->maName, rSysPath, aIt->mbStorage, !aIt->mbStorage );

    // process children of the storage
    for( StorageIterator aIt( rxStrg ); aIt.isValid(); ++aIt )
    {
        // skip processed preferred items
        OUString aItemName = aIt.getName();
        bool bFound = false;
        if( rStrgPath.getLength() == 0 )
            for( PreferredItemVector::iterator aIIt = maPreferred.begin(), aIEnd = maPreferred.end(); !bFound && (aIIt != aIEnd); ++aIIt )
                bFound = aIIt->maName == aItemName;
        if( !bFound )
            extractItem( rxStrg, rStrgPath, aItemName, rSysPath, aIt.isStorage(), aIt.isStream() );
    }
}

void StorageObjectBase::extractItem( const StorageRef& rxStrg, const OUString& rStrgPath, const OUString& rItemName, const OUString& rSysPath, bool bIsStrg, bool bIsStrm )
{
    OUString aSysFileName = getSysFileName( rItemName, rSysPath );
    if( bIsStrg )
    {
        OUStringBuffer aStrgPath( rStrgPath );
        StringHelper::appendToken( aStrgPath, rItemName, '/' );
        implDumpStorage( rxStrg->openSubStorage( rItemName, false ), aStrgPath.makeStringAndClear(), aSysFileName );
    }
    else if( bIsStrm )
    {
        extractStream( *rxStrg, rStrgPath, rItemName, aSysFileName );
    }
}

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

OutputObjectBase::~OutputObjectBase()
{
}

void OutputObjectBase::construct( const ObjectBase& rParent, const OUString& rSysFileName )
{
    ObjectBase::construct( rParent );
    if( ObjectBase::implIsValid() )
        mxOut.reset( new Output( getFactory(), rSysFileName + OOX_DUMP_DUMPEXT ) );
}

void OutputObjectBase::construct( const ObjectBase& rParent, const OutputRef& rxOut )
{
    ObjectBase::construct( rParent );
    mxOut = rxOut;
}

void OutputObjectBase::construct( const OutputObjectBase& rParent )
{
    *this = rParent;
}

bool OutputObjectBase::implIsValid() const
{
    return isValid( mxOut ) && ObjectBase::implIsValid();
}

void OutputObjectBase::writeEmptyItem( const String& rName )
{
    ItemGuard aItem( mxOut, rName );
}

void OutputObjectBase::writeInfoItem( const String& rName, const String& rData )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeString( rData );
}

void OutputObjectBase::writeCharItem( const String& rName, sal_Unicode cData )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeChar( OOX_DUMP_STRQUOTE );
    mxOut->writeChar( cData );
    mxOut->writeChar( OOX_DUMP_STRQUOTE );
}

void OutputObjectBase::writeStringItem( const String& rName, const OUString& rData )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeAscii( "(len=" );
    mxOut->writeDec( rData.getLength() );
    mxOut->writeAscii( ")," );
    OUStringBuffer aValue( rData.copy( 0, ::std::min( rData.getLength(), OOX_DUMP_MAXSTRLEN ) ) );
    StringHelper::enclose( aValue, OOX_DUMP_STRQUOTE );
    mxOut->writeString( aValue.makeStringAndClear() );
    if( rData.getLength() > OOX_DUMP_MAXSTRLEN )
        mxOut->writeAscii( ",cut" );
}

void OutputObjectBase::writeArrayItem( const String& rName, const sal_uInt8* pnData, sal_Size nSize, sal_Unicode cSep )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeArray( pnData, nSize, cSep );
}

void OutputObjectBase::writeBoolItem( const String& rName, bool bData )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeBool( bData );
}

double OutputObjectBase::writeRkItem( const String& rName, sal_Int32 nRk )
{
    MultiItemsGuard aMultiGuard( mxOut );
    writeHexItem( rName, static_cast< sal_uInt32 >( nRk ), "RK-FLAGS" );
    double fValue = ::oox::xls::BiffHelper::calcDoubleFromRk( nRk );
    writeDecItem( "decoded", fValue );
    return fValue;
}

void OutputObjectBase::writeColorABGRItem( const String& rName, sal_Int32 nColor )
{
    ItemGuard aItem( mxOut, rName );
    writeHexItem( rName, nColor );
    mxOut->writeColorABGR( nColor );
}

void OutputObjectBase::writeDateTimeItem( const String& rName, const DateTime& rDateTime )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeDateTime( rDateTime );
}

void OutputObjectBase::writeGuidItem( const String& rName, const OUString& rGuid )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeString( rGuid );
    aItem.cont();
    mxOut->writeString( cfg().getStringOption( rGuid, OUString() ) );
}

void OutputObjectBase::writeColIndexItem( const String& rName, sal_Int32 nCol )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeDec( nCol );
    aItem.cont();
    mxOut->writeColIndex( nCol );
}

void OutputObjectBase::writeRowIndexItem( const String& rName, sal_Int32 nRow )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeDec( nRow );
    aItem.cont();
    mxOut->writeRowIndex( nRow );
}

void OutputObjectBase::writeColRangeItem( const String& rName, sal_Int32 nCol1, sal_Int32 nCol2 )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeColRowRange( nCol1, nCol2 );
    aItem.cont();
    mxOut->writeColRange( nCol1, nCol2 );
}

void OutputObjectBase::writeRowRangeItem( const String& rName, sal_Int32 nRow1, sal_Int32 nRow2 )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeColRowRange( nRow1, nRow2 );
    aItem.cont();
    mxOut->writeRowRange( nRow1, nRow2 );
}

void OutputObjectBase::writeAddressItem( const String& rName, const Address& rPos )
{
    ItemGuard aItem( mxOut, rName );
    StringHelper::appendAddress( mxOut->getLine(), rPos );
}

void OutputObjectBase::writeRangeItem( const String& rName, const Range& rRange )
{
    ItemGuard aItem( mxOut, rName );
    StringHelper::appendRange( mxOut->getLine(), rRange );
}

void OutputObjectBase::writeRangeListItem( const String& rName, const RangeList& rRanges )
{
    MultiItemsGuard aMultiGuard( mxOut );
    writeEmptyItem( rName );
    writeDecItem( "count", static_cast< sal_uInt16 >( rRanges.size() ) );
    ItemGuard aItem( mxOut, "ranges" );
    StringHelper::appendRangeList( mxOut->getLine(), rRanges );
}

void OutputObjectBase::writeTokenAddressItem( const String& rName, const TokenAddress& rPos, bool bNameMode )
{
    ItemGuard aItem( mxOut, rName );
    StringHelper::appendAddress( mxOut->getLine(), rPos, bNameMode );
}

void OutputObjectBase::writeTokenAddress3dItem( const String& rName, const OUString& rRef, const TokenAddress& rPos, bool bNameMode )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeString( rRef );
    StringHelper::appendAddress( mxOut->getLine(), rPos, bNameMode );
}

void OutputObjectBase::writeTokenRangeItem( const String& rName, const TokenRange& rRange, bool bNameMode )
{
    ItemGuard aItem( mxOut, rName );
    StringHelper::appendRange( mxOut->getLine(), rRange, bNameMode );
}

void OutputObjectBase::writeTokenRange3dItem( const String& rName, const OUString& rRef, const TokenRange& rRange, bool bNameMode )
{
    ItemGuard aItem( mxOut, rName );
    mxOut->writeString( rRef );
    StringHelper::appendRange( mxOut->getLine(), rRange, bNameMode );
}

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

InputObjectBase::~InputObjectBase()
{
}

void InputObjectBase::construct( const ObjectBase& rParent, const BinaryInputStreamRef& rxStrm, const OUString& rSysFileName )
{
    OutputObjectBase::construct( rParent, rSysFileName );
    mxStrm = rxStrm;
}

void InputObjectBase::construct( const ObjectBase& rParent, const BinaryInputStreamRef& rxStrm, const OutputRef& rxOut )
{
    OutputObjectBase::construct( rParent, rxOut );
    mxStrm = rxStrm;
}

void InputObjectBase::construct( const OutputObjectBase& rParent, const BinaryInputStreamRef& rxStrm )
{
    OutputObjectBase::construct( rParent );
    mxStrm = rxStrm;
}

void InputObjectBase::construct( const InputObjectBase& rParent )
{
    *this = rParent;
}

bool InputObjectBase::implIsValid() const
{
    return mxStrm.get() && OutputObjectBase::implIsValid();
}

void InputObjectBase::skipBlock( sal_Int64 nBytes, bool bShowSize )
{
    sal_Int64 nEndPos = ::std::min< sal_Int64 >( mxStrm->tell() + nBytes, mxStrm->getLength() );
    if( mxStrm->tell() < nEndPos )
    {
        if( bShowSize )
            writeDecItem( "skipped-data-size", static_cast< sal_uInt64 >( nEndPos - mxStrm->tell() ) );
        mxStrm->seek( nEndPos );
    }
}

void InputObjectBase::dumpRawBinary( sal_Int64 nBytes, bool bShowOffset, bool bStream )
{
    TableGuard aTabGuard( mxOut,
        bShowOffset ? 12 : 0,
        3 * OOX_DUMP_BYTESPERLINE / 2 + 1,
        3 * OOX_DUMP_BYTESPERLINE / 2 + 1,
        OOX_DUMP_BYTESPERLINE / 2 + 1 );

    sal_Int64 nMaxShowSize = cfg().getIntOption< sal_Int64 >(
        bStream ? "max-binary-stream-size" : "max-binary-data-size", SAL_MAX_INT64 );

    bool bSeekable = mxStrm->getLength() >= 0;
    sal_Int64 nEndPos = bSeekable ? ::std::min< sal_Int64 >( mxStrm->tell() + nBytes, mxStrm->getLength() ) : 0;
    sal_Int64 nDumpEnd = bSeekable ? ::std::min< sal_Int64 >( mxStrm->tell() + nMaxShowSize, nEndPos ) : nMaxShowSize;
    sal_Int64 nPos = bSeekable ? mxStrm->tell() : 0;
    bool bLoop = true;

    while( bLoop && (nPos < nDumpEnd) )
    {
        mxOut->writeHex( static_cast< sal_uInt32 >( nPos ) );
        mxOut->tab();

        sal_uInt8 pnLineData[ OOX_DUMP_BYTESPERLINE ];
        sal_Int32 nLineSize = bSeekable ? ::std::min( static_cast< sal_Int32 >( nDumpEnd - mxStrm->tell() ), OOX_DUMP_BYTESPERLINE ) : OOX_DUMP_BYTESPERLINE;
        sal_Int32 nReadSize = mxStrm->readMemory( pnLineData, nLineSize );
        bLoop = nReadSize == nLineSize;
        nPos += nReadSize;

        if( nReadSize > 0 )
        {
            const sal_uInt8* pnByte = 0;
            const sal_uInt8* pnEnd = 0;
            for( pnByte = pnLineData, pnEnd = pnLineData + nReadSize; pnByte != pnEnd; ++pnByte )
            {
                if( (pnByte - pnLineData) == (OOX_DUMP_BYTESPERLINE / 2) ) mxOut->tab();
                mxOut->writeHex( *pnByte, false );
                mxOut->writeChar( ' ' );
            }

            aTabGuard.tab( 3 );
            for( pnByte = pnLineData, pnEnd = pnLineData + nReadSize; pnByte != pnEnd; ++pnByte )
            {
                if( (pnByte - pnLineData) == (OOX_DUMP_BYTESPERLINE / 2) ) mxOut->tab();
                mxOut->writeChar( static_cast< sal_Unicode >( (*pnByte < 0x20) ? '.' : *pnByte ) );
            }
            mxOut->newLine();
        }
    }

    // skip undumped data
    if( bSeekable )
        skipBlock( nEndPos - mxStrm->tell() );
}

void InputObjectBase::dumpBinary( const String& rName, sal_Int64 nBytes, bool bShowOffset )
{
    {
        MultiItemsGuard aMultiGuard( mxOut );
        writeEmptyItem( rName );
        writeDecItem( "size", nBytes );
    }
    IndentGuard aIndGuard( mxOut );
    dumpRawBinary( nBytes, bShowOffset );
}

void InputObjectBase::dumpRemaining( sal_Int64 nBytes )
{
    if( nBytes > 0 )
    {
        if( cfg().getBoolOption( "show-trailing-unknown", true ) )
            dumpBinary( "remaining-data", nBytes, false );
        else
            skipBlock( nBytes );
    }
}

void InputObjectBase::dumpRemainingTo( sal_Int64 nPos )
{
    if( mxStrm->isEof() || (mxStrm->tell() > nPos) )
        writeInfoItem( "stream-state", OOX_DUMP_ERR_STREAM );
    else
        dumpRemaining( nPos - mxStrm->tell() );
    mxStrm->seek( nPos );
}

void InputObjectBase::dumpRemainingStream()
{
    dumpRemainingTo( mxStrm->getLength() );
}

void InputObjectBase::dumpArray( const String& rName, sal_Int32 nBytes, sal_Unicode cSep )
{
    sal_Int32 nDumpSize = getLimitedValue< sal_Int32, sal_Int64 >( mxStrm->getLength() - mxStrm->tell(), 0, nBytes );
    if( nDumpSize > OOX_DUMP_MAXARRAY )
    {
        dumpBinary( rName, nBytes, false );
    }
    else if( nDumpSize > 1 )
    {
        sal_uInt8 pnData[ OOX_DUMP_MAXARRAY ];
        mxStrm->readMemory( pnData, nDumpSize );
        writeArrayItem( rName, pnData, nDumpSize, cSep );
    }
    else if( nDumpSize == 1 )
        dumpHex< sal_uInt8 >( rName );
}

sal_Unicode InputObjectBase::dumpChar( const String& rName, rtl_TextEncoding eTextEnc )
{
    sal_uInt8 nChar;
    *mxStrm >> nChar;
    OUString aChar = OStringToOUString( OString( static_cast< sal_Char >( nChar ) ), eTextEnc );
    sal_Unicode cChar = (aChar.getLength() > 0) ? aChar[ 0 ] : 0;
    writeCharItem( rName( "char" ), cChar );
    return cChar;
}

sal_Unicode InputObjectBase::dumpUnicode( const String& rName )
{
    sal_uInt16 nChar;
    *mxStrm >> nChar;
    sal_Unicode cChar = static_cast< sal_Unicode >( nChar );
    writeCharItem( rName( "char" ), cChar );
    return cChar;
}

OUString InputObjectBase::dumpCharArray( const String& rName, sal_Int32 nLen, rtl_TextEncoding eTextEnc, bool bHideTrailingNul )
{
    sal_Int32 nDumpSize = getLimitedValue< sal_Int32, sal_Int64 >( mxStrm->getLength() - mxStrm->tell(), 0, nLen );
    OUString aString;
    if( nDumpSize > 0 )
    {
        ::std::vector< sal_Char > aBuffer( static_cast< sal_Size >( nLen ) + 1 );
        sal_Int32 nCharsRead = mxStrm->readMemory( &aBuffer.front(), nLen );
        aBuffer[ nCharsRead ] = 0;
        aString = OStringToOUString( OString( &aBuffer.front() ), eTextEnc );
    }
    if( bHideTrailingNul )
        aString = StringHelper::trimTrailingNul( aString );
    writeStringItem( rName( "text" ), aString );
    return aString;
}

OUString InputObjectBase::dumpUnicodeArray( const String& rName, sal_Int32 nLen, bool bHideTrailingNul )
{
    OUStringBuffer aBuffer;
    for( sal_Int32 nIndex = 0; !mxStrm->isEof() && (nIndex < nLen); ++nIndex )
        aBuffer.append( static_cast< sal_Unicode >( mxStrm->readuInt16() ) );
    OUString aString = aBuffer.makeStringAndClear();
    if( bHideTrailingNul )
        aString = StringHelper::trimTrailingNul( aString );
    writeStringItem( rName( "text" ), aString );
    return aString;
}

OUString InputObjectBase::dumpNullCharArray( const String& rName, rtl_TextEncoding eTextEnc )
{
    OStringBuffer aBuffer;
    sal_uInt8 nChar;
    for( *mxStrm >> nChar; !mxStrm->isEof() && (nChar > 0); *mxStrm >> nChar )
        aBuffer.append( static_cast< sal_Char >( nChar ) );
    OUString aString = OStringToOUString( aBuffer.makeStringAndClear(), eTextEnc );
    writeStringItem( rName( "text" ), aString );
    return aString;
}

OUString InputObjectBase::dumpNullUnicodeArray( const String& rName )
{
    OUStringBuffer aBuffer;
    sal_uInt16 nChar;
    for( *mxStrm >> nChar; !mxStrm->isEof() && (nChar > 0); *mxStrm >> nChar )
        aBuffer.append( static_cast< sal_Unicode >( nChar ) );
    OUString aString = aBuffer.makeStringAndClear();
    writeStringItem( rName( "text" ), aString );
    return aString;
}

double InputObjectBase::dumpRk( const String& rName )
{
    sal_Int32 nRk;
    *mxStrm >> nRk;
    return writeRkItem( rName( "rk-value" ), nRk );
}

sal_Int32 InputObjectBase::dumpColorABGR( const String& rName )
{
    sal_Int32 nColor;
    *mxStrm >> nColor;
    writeColorABGRItem( rName( "color" ), nColor );
    return nColor;
}

DateTime InputObjectBase::dumpFileTime( const String& rName )
{
    DateTime aDateTime;

    ItemGuard aItem( mxOut, rName( "file-time" ) );
    sal_Int64 nFileTime = dumpDec< sal_Int64 >( EMPTY_STRING );
    // file time is in 10^-7 seconds (100 nanoseconds), convert to 1/100 seconds
    nFileTime /= 100000;
    // entire days
    sal_Int64 nDays = nFileTime / sal_Int64( 360000 * 24 );
    // number of entire years
    sal_Int64 nYears = (nDays - (nDays / (4 * 365)) + (nDays / (100 * 365)) - (nDays / (400 * 365))) / 365;
    // remaining days in the year
    sal_Int64 nDaysInYear = nDays - (nYears * 365 + nYears / 4 - nYears / 100 + nYears / 400);
    // the year (file dates start from 1601-01-01)
    aDateTime.Year = static_cast< sal_uInt16 >( 1601 + nYears );
    // leap year?
    bool bLeap = ((aDateTime.Year % 4 == 0) && (aDateTime.Year % 100 != 0)) || (aDateTime.Year % 400 == 0);
    // static arrays with number of days in month
    static const sal_Int64 spnDaysInMonth[]  = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    static const sal_Int64 spnDaysInMonthL[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    const sal_Int64* pnDaysInMonth = bLeap ? spnDaysInMonthL : spnDaysInMonth;
    // the month
    aDateTime.Month = 1;
    while( nDaysInYear >= *pnDaysInMonth )
    {
        nDaysInYear -= *pnDaysInMonth++;
        ++aDateTime.Month;
    }
    // the day
    aDateTime.Day = static_cast< sal_uInt16 >( nDaysInYear + 1 );
    // number of 1/100 seconds in the day
    sal_Int64 nTimeInDay = nFileTime % sal_Int64( 360000 * 24 );
    // 1/100 seconds
    aDateTime.HundredthSeconds = static_cast< sal_uInt16 >( nTimeInDay % 100 );
    nTimeInDay /= 100;
    // seconds
    aDateTime.Seconds = static_cast< sal_uInt16 >( nTimeInDay % 60 );
    nTimeInDay /= 60;
    // minutes
    aDateTime.Minutes = static_cast< sal_uInt16 >( nTimeInDay % 60 );
    nTimeInDay /= 60;
    // hours
    aDateTime.Hours = static_cast< sal_uInt16 >( nTimeInDay );

    writeDateTimeItem( EMPTY_STRING, aDateTime );
    return aDateTime;
}

OUString InputObjectBase::dumpGuid( const String& rName )
{
    OUStringBuffer aBuffer;
    sal_uInt32 nData32;
    sal_uInt16 nData16;
    sal_uInt8 nData8;

    *mxStrm >> nData32;
    StringHelper::appendHex( aBuffer, nData32, false );
    aBuffer.append( sal_Unicode( '-' ) );
    *mxStrm >> nData16;
    StringHelper::appendHex( aBuffer, nData16, false );
    aBuffer.append( sal_Unicode( '-' ) );
    *mxStrm >> nData16;
    StringHelper::appendHex( aBuffer, nData16, false );
    aBuffer.append( sal_Unicode( '-' ) );
    *mxStrm >> nData8;
    StringHelper::appendHex( aBuffer, nData8, false );
    *mxStrm >> nData8;
    StringHelper::appendHex( aBuffer, nData8, false );
    aBuffer.append( sal_Unicode( '-' ) );
    for( int nIndex = 0; nIndex < 6; ++nIndex )
    {
        *mxStrm >> nData8;
        StringHelper::appendHex( aBuffer, nData8, false );
    }
    StringHelper::enclose( aBuffer, '{', '}' );
    OUString aGuid = aBuffer.makeStringAndClear();
    writeGuidItem( rName( "guid" ), aGuid );
    return aGuid;
}

void InputObjectBase::dumpItem( const ItemFormat& rItemFmt )
{
    switch( rItemFmt.meDataType )
    {
        case DATATYPE_VOID:                                         break;
        case DATATYPE_INT8:    dumpValue< sal_Int8 >( rItemFmt );   break;
        case DATATYPE_UINT8:   dumpValue< sal_uInt8 >( rItemFmt );  break;
        case DATATYPE_INT16:   dumpValue< sal_Int16 >( rItemFmt );  break;
        case DATATYPE_UINT16:  dumpValue< sal_uInt16 >( rItemFmt ); break;
        case DATATYPE_INT32:   dumpValue< sal_Int32 >( rItemFmt );  break;
        case DATATYPE_UINT32:  dumpValue< sal_uInt32 >( rItemFmt ); break;
        case DATATYPE_INT64:   dumpValue< sal_Int64 >( rItemFmt );  break;
        case DATATYPE_UINT64:  dumpValue< sal_uInt64 >( rItemFmt ); break;
        case DATATYPE_FLOAT:   dumpValue< float >( rItemFmt );      break;
        case DATATYPE_DOUBLE:  dumpValue< double >( rItemFmt );     break;
        default:;
    }
}

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

BinaryStreamObject::BinaryStreamObject( const ObjectBase& rParent, const BinaryInputStreamRef& rxStrm, const OUString& rSysFileName )
{
    InputObjectBase::construct( rParent, rxStrm, rSysFileName );
}

BinaryStreamObject::BinaryStreamObject( const OutputObjectBase& rParent, const BinaryInputStreamRef& rxStrm )
{
    InputObjectBase::construct( rParent, rxStrm );
}

void BinaryStreamObject::dumpBinaryStream( bool bShowOffset )
{
    mxStrm->seekToStart();
    dumpRawBinary( mxStrm->getLength(), bShowOffset, true );
    mxOut->emptyLine();
}

void BinaryStreamObject::implDump()
{
    dumpBinaryStream();
}

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

TextStreamObject::TextStreamObject( const ObjectBase& rParent,
        const BinaryInputStreamRef& rxStrm, rtl_TextEncoding eTextEnc, const OUString& rSysFileName )
{
    InputObjectBase::construct( rParent, rxStrm, rSysFileName );
    if( rxStrm.get() )
        mxTextStrm.reset( new TextInputStream( *rxStrm, eTextEnc ) );
}

TextStreamObject::TextStreamObject( const OutputObjectBase& rParent,
        const BinaryInputStreamRef& rxStrm, rtl_TextEncoding eTextEnc )
{
    InputObjectBase::construct( rParent, rxStrm );
    if( rxStrm.get() )
        mxTextStrm.reset( new TextInputStream( *rxStrm, eTextEnc ) );
}

bool TextStreamObject::implIsValid() const
{
    return InputObjectBase::implIsValid() && mxTextStrm.get();
}

void TextStreamObject::implDump()
{
    OUString aLine;
    sal_uInt32 nLine = 0;
    while( !mxTextStrm->isEof() )
    {
        aLine = mxTextStrm->readLine();
        if( !mxTextStrm->isEof() )
            implDumpLine( aLine, ++nLine );
    }
    mxOut->emptyLine();
}

void TextStreamObject::implDumpLine( const OUString& rLine, sal_uInt32 nLine )
{
    TableGuard aTabGuard( mxOut, 8 );
    mxOut->writeDec( nLine, 6 );
    mxOut->tab();
    mxOut->writeString( rLine );
    mxOut->newLine();
}

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

XmlStreamObject::XmlStreamObject( const ObjectBase& rParent, const BinaryInputStreamRef& rxStrm, const OUString& rSysFileName ) :
    TextStreamObject( rParent, rxStrm, RTL_TEXTENCODING_UTF8, rSysFileName )
{
}

void XmlStreamObject::implDump()
{
    maIncompleteLine = OUString();
    TextStreamObject::implDump();
    if( maIncompleteLine.getLength() > 0 )
    {
        mxOut->resetIndent();
        mxOut->writeString( maIncompleteLine );
        mxOut->emptyLine();
        writeInfoItem( "stream-state", OOX_DUMP_ERR_STREAM );
    }
}

void XmlStreamObject::implDumpLine( const OUString& rLine, sal_uInt32 )
{
    // build input line from cached incomplete element and new text data
    OUStringBuffer aLine;
    if( maIncompleteLine.getLength() > 0 )
        aLine.append( maIncompleteLine ).append( sal_Unicode( ' ' ) );
    aLine.append( rLine );
    maIncompleteLine = OUString();

    if( aLine.getLength() == 0 )
    {
        mxOut->newLine();
        return;
    }

    const sal_Unicode* pcPos = aLine.getStr();
    const sal_Unicode* pcEnd = pcPos + aLine.getLength();
    while( pcPos < pcEnd )
    {
        OUStringBuffer aOutLine;
        bool bIsStartElement = false;
        bool bIsComplElement = false;
        bool bIsEndElement = false;

        /*  check for start element at beginning of the line - pcEnd and thus (pcPos+1)
            are dereferenceable, because OUStringBuffer::getStr is null-terminated. */
        if( (*pcPos == '<') && (pcPos[ 1 ] != '/') )
        {
            const sal_Unicode* pcElementEnd = ::std::find( pcPos, pcEnd, '>' );
            if( pcElementEnd == pcEnd )
            {
                // incomplete start element
                maIncompleteLine = OUString( pcPos, static_cast< sal_Int32 >( pcEnd - pcPos ) );
                pcPos = pcEnd;
            }
            else
            {
                bIsComplElement = (pcPos[ 1 ] == '?') || (pcPos[ 1 ] == '!') || (pcElementEnd[ -1 ] == '/');
                bIsStartElement = !bIsComplElement;
                ++pcElementEnd;
                aOutLine.append( pcPos, static_cast< sal_Int32 >( pcElementEnd - pcPos ) );
                pcPos = pcElementEnd;
            }
        }

        // check for following element text
        if( !bIsComplElement && (pcPos < pcEnd) )
        {
            const sal_Unicode* pcElementStart = ::std::find( pcPos, pcEnd, '<' );
            // append text between elements
            if( pcPos < pcElementStart )
            {
                OUString aText( pcPos, static_cast< sal_Int32 >( pcElementStart - pcPos ) );
                if( aText.trim().getLength() > 0 )
                    aOutLine.append( aText );
                pcPos = pcElementStart;
            }
        }

        // check for stand-alone or following end element
        if( !bIsComplElement && (pcPos < pcEnd) && (pcPos[ 1 ] == '/') )
        {
            const sal_Unicode* pcElementEnd = ::std::find( pcPos, pcEnd, '>' );
            if( pcElementEnd == pcEnd )
            {
                // incomplete end element
                aOutLine.append( pcPos, static_cast< sal_Int32 >( pcEnd - pcPos ) );
                maIncompleteLine = aOutLine.makeStringAndClear();
                pcPos = pcEnd;
            }
            else
            {
                bIsEndElement = true;
                ++pcElementEnd;
                aOutLine.append( pcPos, static_cast< sal_Int32 >( pcElementEnd - pcPos ) );
                pcPos = pcElementEnd;
            }
        }

        // flush output line
        if( maIncompleteLine.getLength() == 0 )
        {
            if( !bIsStartElement && bIsEndElement ) mxOut->decIndent();
            mxOut->writeString( aOutLine.makeStringAndClear() );
            mxOut->newLine();
            if( bIsStartElement && !bIsEndElement ) mxOut->incIndent();
        }
    }
}

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

void RecordObjectBase::construct( const ObjectBase& rParent,
        const BinaryInputStreamRef& rxBaseStrm, const OUString& rSysFileName,
        const BinaryInputStreamRef& rxRecStrm, const String& rRecNames, const String& rSimpleRecs )
{
    InputObjectBase::construct( rParent, rxRecStrm, rSysFileName );
    constructRecObjBase( rxBaseStrm, rRecNames, rSimpleRecs );
}

void RecordObjectBase::construct( const OutputObjectBase& rParent, const BinaryInputStreamRef& rxBaseStrm,
        const BinaryInputStreamRef& rxRecStrm, const String& rRecNames, const String& rSimpleRecs )
{
    InputObjectBase::construct( rParent, rxRecStrm );
    constructRecObjBase( rxBaseStrm, rRecNames, rSimpleRecs );
}

bool RecordObjectBase::implIsValid() const
{
    return mxBaseStrm.get() && InputObjectBase::implIsValid();
}

void RecordObjectBase::implDump()
{
    NameListRef xRecNames = getRecNames();
    ItemFormatMap aSimpleRecs( maSimpleRecs.getNameList( cfg() ) );

    while( implStartRecord( *mxBaseStrm, mnRecPos, mnRecId, mnRecSize ) )
    {
        // record header
        mxOut->emptyLine();
        writeHeader();
        implWriteExtHeader();
        IndentGuard aIndGuard( mxOut );
        sal_Int64 nRecPos = mxStrm->tell();

        // record body
        if( !mbBinaryOnly && cfg().hasName( xRecNames, mnRecId ) )
        {
            ItemFormatMap::const_iterator aIt = aSimpleRecs.find( mnRecId );
            if( aIt != aSimpleRecs.end() )
                dumpItem( aIt->second );
            else
                implDumpRecordBody();
        }

        // remaining undumped data
        if( !mxStrm->isEof() && (mxStrm->tell() == nRecPos) )
            dumpRawBinary( mnRecSize, false );
        else
            dumpRemainingTo( nRecPos + mnRecSize );
    }
}

void RecordObjectBase::implWriteExtHeader()
{
}

void RecordObjectBase::implDumpRecordBody()
{
}

void RecordObjectBase::constructRecObjBase( const BinaryInputStreamRef& rxBaseStrm, const String& rRecNames, const String& rSimpleRecs )
{
    mxBaseStrm = rxBaseStrm;
    maRecNames = rRecNames;
    maSimpleRecs = rSimpleRecs;
    mnRecPos = mnRecId = mnRecSize = 0;
    mbBinaryOnly = false;
    if( InputObjectBase::implIsValid() )
        mbShowRecPos = cfg().getBoolOption( "show-record-position", true );
}

void RecordObjectBase::writeHeader()
{
    MultiItemsGuard aMultiGuard( mxOut );
    writeEmptyItem( "REC" );
    if( mbShowRecPos && mxBaseStrm->isSeekable() )
        writeShortHexItem( "pos", mnRecPos, "CONV-DEC" );
    writeShortHexItem( "size", mnRecSize, "CONV-DEC" );
    ItemGuard aItem( mxOut, "id" );
    mxOut->writeShortHex( mnRecId );
    addNameToItem( mnRecId, "CONV-DEC" );
    addNameToItem( mnRecId, maRecNames );
}

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

void SequenceRecordObjectBase::construct( const ObjectBase& rParent,
        const BinaryInputStreamRef& rxBaseStrm, const ::rtl::OUString& rSysFileName,
        const String& rRecNames, const String& rSimpleRecs )
{
    BinaryInputStreamRef xRecStrm( new SequenceInputStream( *mxRecData ) );
    RecordObjectBase::construct( rParent, rxBaseStrm, rSysFileName, xRecStrm, rRecNames, rSimpleRecs );
}

void SequenceRecordObjectBase::construct( const OutputObjectBase& rParent,
        const BinaryInputStreamRef& rxBaseStrm, const String& rRecNames, const String& rSimpleRecs )
{
    BinaryInputStreamRef xRecStrm( new SequenceInputStream( *mxRecData ) );
    RecordObjectBase::construct( rParent, rxBaseStrm, xRecStrm, rRecNames, rSimpleRecs );
}

bool SequenceRecordObjectBase::implStartRecord( BinaryInputStream& rBaseStrm, sal_Int64& ornRecPos, sal_Int64& ornRecId, sal_Int64& ornRecSize )
{
    bool bValid = true;
    if( rBaseStrm.isSeekable() )
    {
        ornRecPos = rBaseStrm.tell();
        // do not try to overread seekable streams, may cause assertions
        bValid = ornRecPos < rBaseStrm.getLength();
    }

    // read the record header
    if( bValid )
        bValid = implReadRecordHeader( rBaseStrm, ornRecId, ornRecSize ) && !rBaseStrm.isEof() && (0 <= ornRecSize) && (ornRecSize <= 0x00100000);

    // read record contents into data sequence
    if( bValid )
    {
        sal_Int32 nRecSize = static_cast< sal_Int32 >( ornRecSize );
        mxRecData->realloc( nRecSize );
        bValid = (nRecSize == 0) || (rBaseStrm.readData( *mxRecData, nRecSize ) == nRecSize);
        mxStrm->seekToStart();
    }
    return bValid;
}

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

DumperBase::~DumperBase()
{
}

bool DumperBase::isImportEnabled() const
{
    return !isValid() || cfg().isImportEnabled();
}

bool DumperBase::isImportCancelled() const
{
    return isValid() && cfg().isPasswordCancelled();
}

void DumperBase::construct( const ConfigRef& rxConfig )
{
    if( isValid( rxConfig ) && rxConfig->isDumperEnabled() )
        ObjectBase::construct( rxConfig );
}

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

} // namespace dump
} // namespace oox

#endif

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