/*
* Copyright (c) 2004 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  XML Serializer implementation.
*
*/

// INCLUDE FILES
#include "CPEngXmlSerializer.h"
#include "PEngXmlDefs.h"
#include "PresenceDebugPrint.h"


#ifdef __SERIALIZER_TAG_NAME_ASSERT
#include "CPEngTagAssertionStack.h"
#endif

#include <E32Std.h>
#include <utf.h>
#include <imcvcodc.h>



//CONSTANTS
/**
 * State stack granularity.
 * State stack has great size
 * variety depending from use case.
 * 5 should be quite optimal.
 */
const TInt KStackGranurality = 5;

/**
 * WV schema prefix & length.
 */
_LIT( KWVSchemaPrefix, "wv:" );
const TInt KWVSchemaPrefixLength = 3;

// ================= LOCAL FUNCTIONS =======================
/**
 * Serializer panic implementation.
 */
#if defined(_DEBUG)
GLREF_C void PanicSerializer( TPEngSerializerPanics aReason )
    {
    User::Panic( KXmlSerializer, aReason );
    }
#else
GLREF_C void PanicSerializer( TPEngSerializerPanics /*aReason*/ )
    {
    }
#endif




// ================= MEMBER FUNCTIONS =======================
// Two-phased constructor.
CPEngXmlSerializer* CPEngXmlSerializer::NewL( TDes8& aBuffer )
    {
    CPEngXmlSerializer* self = CPEngXmlSerializer::NewLC( aBuffer );
    CleanupStack::Pop( self );
    return self;
    }



CPEngXmlSerializer* CPEngXmlSerializer::NewLC( TDes8& aBuffer )
    {
    CPEngXmlSerializer* self = new ( ELeave ) CPEngXmlSerializer( aBuffer );

    CleanupStack::PushL( self );
    self->ConstructL();

    return self;
    }

// Destructor
CPEngXmlSerializer::~CPEngXmlSerializer()
    {
    iStateStack.Reset();

#ifdef __SERIALIZER_TAG_NAME_ASSERT
    delete iAssertionStack;
#endif //   __SERIALIZER_TAG_NAME_ASSERT


#if _BullseyeCoverage
    cov_write();
#endif
    }


// C++ default constructor can NOT contain any code, that
// might leave.
//
CPEngXmlSerializer::CPEngXmlSerializer( TDes8& aBuffer )
        : iState( EPEngInRoot ),
        iWriter( aBuffer ),
        iStartTagCount( 0 ),
        iStateStack( KStackGranurality )
    {
    }


// Symbian OS default constructor can leave.
void CPEngXmlSerializer::ConstructL()
    {
#ifdef __SERIALIZER_TAG_NAME_ASSERT
    iAssertionStack = CPEngTagAssertionStack::NewL( KStackGranurality );
#endif //   __SERIALIZER_TAG_NAME_ASSERT
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::Close()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::Close()
    {
    delete this;
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::StartTagL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::StartTagL( const TDesC8& aName )
    {
    //tag name may not be empty
    __AssertNotEmptyL( aName );


#ifdef __SERIALIZER_TAG_NAME_ASSERT
    //make sure that if we can write the start tag,
    //we get it surely to element stack also
    iAssertionStack->ReserveOneL();
#endif //   __SERIALIZER_TAG_NAME_ASSERT


    CheckAndCloseOpenStartTagL(); //goes to EElementOpen state
    iWriter.WriteL( KXmlTagStart );


#ifdef __SERIALIZER_TAG_NAME_ASSERT
    TPtrC8 writtenTag( iWriter.WriteL( aName ) );
    iAssertionStack->PushL( writtenTag ); //won't fail, see previous ReserveOneL()
#else
    iWriter.WriteL( aName );
#endif //   __SERIALIZER_TAG_NAME_ASSERT

    iStartTagCount++;
    iState = EPEngInStartTag;

    return *this;
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::AttributeL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::AttributeL( const TDesC8& aName,
                                                    const TDesC8& aValue )
    {
    //attribute can be written only just after the start tag.
    __AssertSerializerStateL( ( iState == EPEngInStartTag ),
                              EPEngSrlzr_AttributeNotAllowed );

    //attribute name may not be empty
    __AssertNotEmptyL( aName );


    iWriter.WriteL( KXmlWhiteSpace );
    iWriter.WriteL( aName );
    iWriter.WriteL( KXmlEqualSign );

    TBuf8< 1 > quote; //quote is one character, either ' or "
    quote = KXmlApostrophe;
    if ( aValue.Find( KXmlDoubleQuote ) == KErrNotFound )
        {
        quote = KXmlDoubleQuote;
        }

    iWriter.WriteL( quote );
    WriteXmlEscapedL( aValue, ETrue );
    iWriter.WriteL( quote );


    return *this;
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::EndTagL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::EndTagL( const TDesC8& aName )
    {
    //Check there is start tag to close
    __AssertSerializerStateL( ( ( iState == EPEngInStartTag ) || ( iState == EPEngInElement ) ),
                              EPEngSrlzr_EndTagNotAllowed );

    //tag name may not be empty
    __AssertNotEmptyL( aName );


#ifdef __SERIALIZER_TAG_NAME_ASSERT
    //Check end tag name match corresponding start tag name
    __AssertEndTagName( aName );
#endif //   __SERIALIZER_TAG_NAME_ASSERT



    if ( iState == EPEngInStartTag )
        {
        //still in start tag ==> empty element ==> close the tag directly
        iWriter.WriteL( KXmlEmptyTagEnd );
        }


    else
        {
        //non empty element ==> write complete end tag
        iWriter.WriteL( KXmlEndTagStart );
        iWriter.WriteL( aName );
        iWriter.WriteL( KXmlTagEnd );
        }

#ifdef __SERIALIZER_TAG_NAME_ASSERT
    iAssertionStack->Pop();
#endif //   __SERIALIZER_TAG_NAME_ASSERT


    iStartTagCount--;
    if ( iStartTagCount == 0 )
        {
        //closed the last open element ==> now in root
        iState = EPEngInRoot;
        }
    else
        {
        //there is still not closed start tags ==> open elements
        iState = EPEngInElement;
        }


    return *this;
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::RawValueL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::RawValueL( const TDesC8& aValue )
    {
    //Write raw data

    CheckAndCloseOpenStartTagL();   //goes to EElementOpen state

    __AssertNoXmlEscapedCharsL( aValue );

    iWriter.WriteL( aValue );

    return *this;
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::NarrowTextL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::NarrowTextL( const TDesC8& aText )
    {
    // Performed steps:
    // 1. Escapes XML entities.


    CheckAndCloseOpenStartTagL();   //goes to EElementOpen state

    WriteXmlEscapedL( aText, EFalse );
    return *this;
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::UnicodeTextL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::UnicodeTextL( const TDesC16& aText )
    {
    // Performed steps:
    // 1. Converts text from Unicode to Utf8
    // 2. Escapes XML entities

    TBuf8<20> outputBuffer;
    TPtrC16 remainderOfUnicodeText( aText );
    TInt returnValue;

    CheckAndCloseOpenStartTagL();   //goes to EElementOpen state


    FOREVER // conversion loop
        {
        returnValue = CnvUtfConverter::ConvertFromUnicodeToUtf8( outputBuffer,
        remainderOfUnicodeText );

        // check to see that the descriptor isn’t corrupt
        if ( returnValue == CnvUtfConverter::EErrorIllFormedInput )
            {
            User::Leave( KErrCorrupt );
            }

        else if ( returnValue < KErrNone ) // future-proof against "TError" expanding
            // See SDK help for more information.
            {
            User::Leave( KErrGeneral );
            }

        //write the Utf8 fragment produced by this round
        WriteXmlEscapedL( outputBuffer, EFalse );

        if ( returnValue == 0 )
            {
            //no more text left -> break the loop
            break;
            }

        remainderOfUnicodeText.Set( remainderOfUnicodeText.Right( returnValue ) );
        }

    return *this;
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::WvAddressL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::WvAddressL( const TDesC16& aAddress )
    {
    // Performed steps:
    // 1. Escapes WV Address characters
    // 2. Converts text from Unicode to Utf8
    // 3. Escape XML entities


    TBuf16<20> outputBuffer;
    TPtrC16 remainderOfAddress( aAddress );
    TInt returnValue;

    CheckAndCloseOpenStartTagL();   //goes to EElementOpen state


    // Skip the possible "wv:" prefix from address
    // And write it as it is
    if ( aAddress.Left( KWVSchemaPrefixLength ).CompareF( KWVSchemaPrefix ) == 0 )
        {
        remainderOfAddress.Set( aAddress.Mid( KWVSchemaPrefixLength ) );
        UnicodeTextL( aAddress.Left( KWVSchemaPrefixLength ) );
        }

    FOREVER // conversion loop
        {
        returnValue = EncodeWvAddressChars( outputBuffer,
        remainderOfAddress );

        // Handle possible errors
        if ( returnValue < KErrNone )
            {
            User::Leave( KErrGeneral );
            }


        //write the WVAddress part produced by this round
        UnicodeTextL( outputBuffer );


        if ( returnValue == 0 )
            {
            //no more text left -> break the loop
            break;
            }

        remainderOfAddress.Set( remainderOfAddress.Right( returnValue ) );
        }

    return *this;
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::Base64DataL()
// -----------------------------------------------------------------------------
//
MPEngXMLSerializer& CPEngXmlSerializer::Base64DataL( const TDesC8& aRawData )
    {
    // Performed steps:
    // 1. Coverts data to BASE64 format


    CheckAndCloseOpenStartTagL();   //goes to EElementOpen state

    if ( aRawData.Length() != 0 )
        {
        TImCodecB64 base64Encoder;
        base64Encoder.Initialise();

        //Encoder doesn't itself cope with buffer overflows,
        //so allocate twice big buffer to be sure that there is enough
        //space for B64 data
        HBufC8* base64Buffer = HBufC8::NewLC( aRawData.Size() * 2 );
        TPtr8 base64Data( base64Buffer->Des() );
        base64Encoder.Encode( aRawData, base64Data );


        //for performance reasons, write
        //whole base64 data directly to the
        //xml buffer (no need to do any XML escaping)
        iWriter.WriteL( base64Data );
        CleanupStack::PopAndDestroy(); //base64Buffer
        }

    return *this;
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::PushSerializerStateL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::PushSerializerStateL()
    {
    TPEngSerializerStateData state;
    state.iWriterLength = iWriter.CurrentLength();
    state.iState =  iState;


#ifdef __SERIALIZER_TAG_NAME_ASSERT
    state.iAssertionStackCount = iAssertionStack->Count();
#endif //   __SERIALIZER_TAG_NAME_ASSERT


    User::LeaveIfError( iStateStack.Append( state ) ); //push new state to state stack
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::CommitState()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::CommitState()
    {
    //check that there is a states to commit
    __ASSERT_DEBUG( ( iStateStack.Count() > 0 ),
                    PanicSerializer( EPEngSrlzr_StateStackUnderflow ) );

    TInt stateCount( iStateStack.Count() );
    if ( stateCount > 0 )
        {
        iStateStack.Remove( stateCount - 1 ); //pop the last state away from state stack (thus -1)
        }
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::RollbackState()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::RollbackState()
    {
    //Check that there is a states to rollback
    __ASSERT_DEBUG( ( iStateStack.Count() > 0 ),
                    PanicSerializer( EPEngSrlzr_StateStackUnderflow ) );

    TInt stateCount( iStateStack.Count() );
    if ( stateCount > 0 )
        {
        TPEngSerializerStateData oldState;
        oldState = iStateStack[ stateCount - 1 ];
        iStateStack.Remove( stateCount - 1 ); //pop the last state away from state stack (thus -1)

        //rollback writer
        iWriter.ReverseTo( oldState.iWriterLength );


#ifdef __SERIALIZER_TAG_NAME_ASSERT
        //rollback assertion stack
        iAssertionStack->PopTo( oldState.iAssertionStackCount );
#endif //   __SERIALIZER_TAG_NAME_ASSERT


        //return to old state
        iState = oldState.iState;
        }
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::PushedStateCount()
// -----------------------------------------------------------------------------
//
TInt CPEngXmlSerializer::PushedStateCount()
    {
    return iStateStack.Count();
    }



// -----------------------------------------------------------------------------
// CPEngXmlSerializer::CheckAndCloseOpenStartTagL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::CheckAndCloseOpenStartTagL()
    {
    if ( iState == EPEngInStartTag )
        {
        iWriter.WriteL( KXmlTagEnd );
        iState = EPEngInElement;
        }
    }




// -----------------------------------------------------------------------------
// CPEngXmlSerializer::EncodeWvAddressChars()
// Return the number of unconverted characters left at the end of the input
// descriptor, or the error value
// -----------------------------------------------------------------------------
//
TInt CPEngXmlSerializer::EncodeWvAddressChars( TDes16& aEncodedAddress,
                                               const TDesC16& aUnicodeAddress )
    {
    if ( aUnicodeAddress.Length() == 0 )
        {
        aEncodedAddress.SetLength( 0 );
        return 0;
        }


    if ( aEncodedAddress.MaxLength() == 0 )
        {
        return aUnicodeAddress.Length();
        }

    aEncodedAddress.Zero();

    TInt addLen( aUnicodeAddress.Length() );
    for ( TInt ii( 0 ); ii < addLen; ii++ )
        {
        TUint16 characterValue = aUnicodeAddress[ ii ];

        switch ( characterValue )
            {
            case ':':
            case ';':
            case '?':
            case '&':
            case '=':
            case '+':
            case '$':
            case ',':
                {
                TInt spaceLeft = aEncodedAddress.MaxLength() - aEncodedAddress.Length();

                //Encoding special characters produces 3 characters e.g. "%yy"
                //(only three produced because encoded characters list is
                //limited above)
                if ( spaceLeft < 3 )
                    {
                    return addLen - ii;
                    }

                aEncodedAddress.Append( KPercentSign16 );
                aEncodedAddress.AppendNumUC( characterValue, EHex );
                break;
                }

            default:
                {
                TInt spaceLeft = aEncodedAddress.MaxLength() - aEncodedAddress.Length();

                //Adding one non-encoded character requires space one character
                if ( spaceLeft < 1 )
                    {
                    return addLen - ii;
                    }

                aEncodedAddress.Append( characterValue );
                break;
                }
            }
        }


    return KErrNone;
    }





// -----------------------------------------------------------------------------
// CPEngXmlSerializer::WriteXmlEscapedL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::WriteXmlEscapedL( const TDesC8& aString,
                                           TBool aEscapeQuotes )
    {
    const TInt length( aString.Length() );
    TUint8 character;

    //loop trough the all characters and escape them to writer one by one
    for ( TInt ii( 0 ); ii < length; ii++ )
        {
        character = aString[ ii ];
        WriteXmlEscapedL( character, aEscapeQuotes );
        }
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::WriteXmlEscapedL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::WriteXmlEscapedL( TUint8 aCharacter,
                                           TBool aEscapeQuotes )
    {
    switch ( aCharacter )
        {
        case '&':
            {
            iWriter.WriteL( KEntityAmpersand );
            break;
            }

        case '>':
            {
            iWriter.WriteL( KEntityGreaterThan );
            break;
            }

        case '<':
            {
            iWriter.WriteL( KEntityLowerThan );
            break;
            }

        case '"':       ///<If character is a double
        case '\'':      ///<or single quote, escape it only if required
            {
            if ( aEscapeQuotes )
                {
                if ( aCharacter == '"' )
                    {
                    iWriter.WriteL( KEntityDoubleQuote );
                    }
                else
                    {
                    iWriter.WriteL( KEntityApostrophe );
                    }

                break;
                }

            //else no need to escape quotes ==>
            //write them as normal characters
            //FLOW TROUGH
            }

        default:
            {
            iWriter.WriteL( aCharacter );
            }
        }
    }





// -----------------------------------------------------------------------------
// CPEngXmlSerializer::__AssertNoXmlEscapedCharsL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::__AssertNoXmlEscapedCharsL( const TDesC8& aString )
    {
    const TInt length( aString.Length() );
    TUint8 character;

    //loop trough the all characters and check them
    for ( TInt ii( 0 ); ii < length; ii++ )
        {
        character = aString[ ii ];
        switch ( character )
            {
            case '&':
            case '>':
            case '<':
            case '"':
            case '\'':
                {
#if defined(_DEBUG)
                PanicSerializer( EPEngSrlzr_XmlMarkupCharNotAllowed );
#else
                User::Leave( KErrArgument );
#endif

                break;
                }

            default:
                {
                break;
                }
            }
        }
    }





// -----------------------------------------------------------------------------
// CPEngXmlSerializer::__AssertSerializerStateL()
// -----------------------------------------------------------------------------
//
#if defined(_DEBUG)
void CPEngXmlSerializer::__AssertSerializerStateL( TBool aInCorrectState,
                                                   TPEngSerializerPanics aPanicReason )
#else
void CPEngXmlSerializer::__AssertSerializerStateL( TBool aInCorrectState,
                                                   TPEngSerializerPanics /*aPanicReason*/ )
#endif
    {
    if ( !aInCorrectState )
        {
#ifdef __SERIALIZER_TAG_NAME_ASSERT
        TBuf<150> buffer16; //restrict tag names shown in debug output to 150 characters.
        //150 characters should be enough for debug purposes.

        TPtrC8 startTagName = iAssertionStack->Top();
        buffer16.Copy( startTagName.Left( buffer16.MaxLength() ) );

        PENG_DP( D_PENG_LIT( "CPEngXmlSerializer: Wrong state. In tag <%S> at nesting level %i" ),
                 &buffer16, iAssertionStack->Count() );
#endif // __SERIALIZER_TAG_NAME_ASSERT


#if defined(_DEBUG)
        PanicSerializer( aPanicReason );
#else
        User::Leave( KErrNotSupported );
#endif
        }
    }


// -----------------------------------------------------------------------------
// CPEngXmlSerializer::__AssertNotEmptyL()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::__AssertNotEmptyL( const TDesC8& aString )
    {
    if ( aString.Length() == 0 )
        {
#ifdef __SERIALIZER_TAG_NAME_ASSERT
        TBuf<150> buffer16; //restrict tag names shown in debug output to 150 characters.
        //150 characters should be enough for debug purposes.

        TPtrC8 startTagName = iAssertionStack->Top();
        buffer16.Copy( startTagName.Left( buffer16.MaxLength() ) );

        PENG_DP( D_PENG_LIT( "CPEngXmlSerializer: Writing empty tag or attribute. In tag <%S> at nesting level %i" ),
                 &buffer16, iAssertionStack->Count() );
#endif // __SERIALIZER_TAG_NAME_ASSERT


#if defined(_DEBUG)
        PanicSerializer( EPEngSrlzr_EmptyInputString );
#else
        User::Leave( KErrArgument );
#endif
        }
    }


#ifdef __SERIALIZER_TAG_NAME_ASSERT
// -----------------------------------------------------------------------------
// CPEngXmlSerializer::__AssertEndTagName()
// -----------------------------------------------------------------------------
//
void CPEngXmlSerializer::__AssertEndTagName( const TDesC8& aEndTagName )
    {
    if ( aEndTagName != iAssertionStack->Top() )
        {
        TBuf<150> buffer16; //restrict tag names shown in debug output to 150 characters.
        //150 characters should be enough for debug purposes.

        buffer16.Copy( aEndTagName.Left( buffer16.MaxLength() ) );
        PENG_DP( D_PENG_LIT( "CPEngXmlSerializer: End tag mismatch. Ending with <%S> at level %i" ),
                 &buffer16, iAssertionStack->Count() );

        buffer16.Zero();
        TPtrC8 startTagName = iAssertionStack->Top();
        buffer16.Copy( startTagName.Left( buffer16.MaxLength() ) );
        PENG_DP( D_PENG_LIT( "CPEngXmlSerializer: Required start tag <%S>" ),
                 &buffer16 );

        PanicSerializer( EPEngSrlzr_EndTagNameMismatch );
        }
    }
#endif // __SERIALIZER_TAG_NAME_ASSERT


//  End of File




