/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_l10ntools.hxx"
#include <tools/string.hxx>
#include "tagtest.hxx"

#if OSL_DEBUG_LEVEL > 1
#include <stdio.h>
#endif

#include "gsicheck.hxx"

#define HAS_FLAG( nFlags, nFlag )		( ( nFlags & nFlag ) != 0 )
#define SET_FLAG( nFlags, nFlag )		( nFlags |= nFlag )
#define RESET_FLAG( nFlags, nFlag )		( nFlags &= ~nFlag )	// ~ = Bitweises NOT



TokenInfo::TokenInfo( TokenId pnId, sal_uInt16 nP, String paStr, ParserMessageList &rErrorList )
: bClosed(sal_False)
, bCloseTag(sal_False)
, bIsBroken(sal_False)
, bHasBeenFixed(sal_False)
, bDone(sal_False)
, aTokenString( paStr )
, nId( pnId )
, nPos(nP)
{
    if ( nId == TAG_COMMONSTART || nId == TAG_COMMONEND )
        SplitTag( rErrorList );
}

enum tagcheck { TC_START, TC_HAS_TAG_NAME, TC_HAS_PROP_NAME_EQ, TC_HAS_PROP_NAME_EQ_SP, TC_HAS_PROP_NAME_SP, TC_INSIDE_STRING, TC_PROP_FINISHED, TC_CLOSED, TC_CLOSED_SPACE, TC_CLOSETAG, TC_CLOSETAG_HAS_TAG_NAME, TC_FINISHED, TC_ERROR };

/*
                                                      \<  link  href  =  \"text\"  name  =  \"C\"  \>
START               ' ' ->  HAS_TAG_NAME
START               '/' ->  CLOSED
START               '/' ->  CLOSETAG    - no Portion (starting with /)
START               '>' ->  FINISHED
HAS_TAG_NAME        '=' ->  HAS_PROP_NAME_EQ
HAS_TAG_NAME        ' ' ->  HAS_PROP_NAME_SP
HAS_TAG_NAME        '/' ->  CLOSED
HAS_TAG_NAME        '>' ->  FINISHED
HAS_PROP_NAME_SP    '=' ->  HAS_PROP_NAME_EQ
HAS_PROP_NAME_EQ    ' ' ->  HAS_PROP_NAME_EQ_SP
HAS_PROP_NAME_EQ    '"' ->  INSIDE_STRING
HAS_PROP_NAME_EQ_SP '"' ->  INSIDE_STRING
INSIDE_STRING       ' ' ->  INSIDE_STRING
INSIDE_STRING       '=' ->  INSIDE_STRING
INSIDE_STRING       '>' ->  INSIDE_STRING
INSIDE_STRING       '"' ->  PROP_FINISHED
PROP_FINISHED       ' ' ->  HAS_TAG_NAME
PROP_FINISHED       '/' ->  CLOSED
PROP_FINISHED       '>' ->  FINISHED
CLOSED              ' ' ->  CLOSED_SPACE
CLOSED              '>' ->  FINISHED
CLOSED_SPACE        '>' ->  FINISHED

CLOSETAG            ' ' ->  CLOSETAG_HAS_TAG_NAME
CLOSETAG            '>' ->  FINISHED
CLOSETAG_HAS_TAG_NAME  '>' ->  FINISHED

*/
void TokenInfo::SplitTag( ParserMessageList &rErrorList )
{
    sal_uInt16 nLastPos = 2;    // skip initial  \<
    sal_uInt16 nCheckPos = nLastPos;
    String aDelims( String::CreateFromAscii( " \\=>/" ) );
    String aPortion;
    String aValue;      // store the value of a property
    ByteString aName;   // store the name of a property/tag 
    sal_Bool bCheckName = sal_False;
    sal_Bool bCheckEmpty = sal_False;
    sal_Unicode cDelim;
    tagcheck aState = TC_START;

    // skip blanks
    while ( nLastPos < aTokenString.Len() && aTokenString.GetChar( nLastPos ) == ' ')
        nLastPos++;

    nCheckPos = aTokenString.SearchChar( aDelims.GetBuffer(), nLastPos );
    while ( nCheckPos != STRING_NOTFOUND && !( aState == TC_FINISHED || aState == TC_ERROR ) )
    {
        aPortion = aTokenString.Copy( nLastPos, nCheckPos-nLastPos );

        if ( aTokenString.GetChar( nCheckPos ) == '\\' )
            nCheckPos++;         

        cDelim = aTokenString.GetChar( nCheckPos );
        nCheckPos++;

        switch ( aState )
        {
//            START           ' ' ->  HAS_TAG_NAME
//            START           '/' ->  CLOSED
//            START           '>' ->  FINISHED
            case TC_START:
                aTagName = aPortion;
                switch ( cDelim )
                {
                    case ' ':  aState = TC_HAS_TAG_NAME;
                               bCheckName = sal_True;
                               break;
                    case '/':  
                        {
                            if ( aPortion.Len() == 0 )
                            {
                                aState = TC_CLOSETAG;
                            }
                            else
                            {
                                aState = TC_CLOSED;
                                bCheckName = sal_True;
                            }
                        }
                        break;
                    case '>':  aState = TC_FINISHED;
                               bCheckName = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            HAS_TAG_NAME    '=' ->  HAS_PROP_NAME_EQ
//            HAS_TAG_NAME    ' ' ->  HAS_PROP_NAME_SP
//            HAS_TAG_NAME    '/' ->  CLOSED
//            HAS_TAG_NAME    '>' ->  FINISHED
            case TC_HAS_TAG_NAME:
                switch ( cDelim )
                {
                    case '=':  aState = TC_HAS_PROP_NAME_EQ;
                               bCheckName = sal_True;
                               break;
                    case ' ':  aState = TC_HAS_PROP_NAME_SP;
                               bCheckName = sal_True;
                               break;
                    case '/':  aState = TC_CLOSED;
                               bCheckEmpty = sal_True;
                               break;
                    case '>':  aState = TC_FINISHED;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            HAS_PROP_NAME_SP    '=' ->  HAS_PROP_NAME_EQ
            case TC_HAS_PROP_NAME_SP:
                switch ( cDelim )
                {
                    case '=':  aState = TC_HAS_PROP_NAME_EQ;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            HAS_PROP_NAME_EQ    ' ' ->  HAS_PROP_NAME_EQ_SP
//            HAS_PROP_NAME_EQ    '"' ->  INSIDE_STRING
            case TC_HAS_PROP_NAME_EQ:
                switch ( cDelim )
                {
                    case ' ':  aState = TC_HAS_PROP_NAME_EQ_SP;
                               bCheckEmpty = sal_True;
                               break;
                    case '\"': aState = TC_INSIDE_STRING;
                               bCheckEmpty = sal_True;
                               aValue.Erase();
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            HAS_PROP_NAME_EQ_SP '"' ->  INSIDE_STRING
            case TC_HAS_PROP_NAME_EQ_SP:
                switch ( cDelim )
                {
                    case '\"': aState = TC_INSIDE_STRING;
                               bCheckEmpty = sal_True;
                               aValue.Erase();
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            INSIDE_STRING    *  ->  INSIDE_STRING
//            INSIDE_STRING   '"' ->  PROP_FINISHED
            case TC_INSIDE_STRING:
                switch ( cDelim )
                {
                    case '\"': 
                        {
                            aState = TC_PROP_FINISHED;
                            aValue += aPortion;
                            if ( aProperties.find( aName ) == aProperties.end() )
                            {
                                if ( !IsPropertyValueValid( aName, aValue ) )
                                {
                                    rErrorList.AddError( 25, ByteString("Property '").Append(aName).Append("' has invalid value '").Append(ByteString( aValue, RTL_TEXTENCODING_UTF8 )).Append("' "), *this );
                                    bIsBroken = sal_True;
                                }
                                aProperties[ aName ] = aValue;
                            }
                            else
                            {
                                rErrorList.AddError( 25, ByteString("Property '").Append(aName).Append("' defined twice "), *this );
                                bIsBroken = sal_True;
                            }
                        }
                               break;
                    default:   
                        {
                            aState = TC_INSIDE_STRING;
                            aValue += aPortion;
                            aValue += cDelim;
                        }
                }
                break;

//            PROP_FINISHED   ' ' ->  HAS_TAG_NAME
//            PROP_FINISHED   '/' ->  CLOSED
//            PROP_FINISHED   '>' ->  FINISHED
            case TC_PROP_FINISHED:
                switch ( cDelim )
                {
                    case ' ': aState = TC_HAS_TAG_NAME;
                               bCheckEmpty = sal_True;
                               break;
                    case '/': aState = TC_CLOSED;
                               bCheckEmpty = sal_True;
                               break;
                    case '>': aState = TC_FINISHED;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            CLOSED          ' ' ->  CLOSED_SPACE
//            CLOSED          '>' ->  FINISHED
            case TC_CLOSED:
                switch ( cDelim )
                {
                    case ' ': aState = TC_CLOSED_SPACE;
                               bCheckEmpty = sal_True;
                               bClosed = sal_True;
                               break;
                    case '>': aState = TC_FINISHED;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

//            CLOSED_SPACE    '>' ->  FINISHED
            case TC_CLOSED_SPACE:
                switch ( cDelim )
                {
                    case '>': aState = TC_FINISHED;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

// CLOSETAG            ' ' ->  CLOSETAG_HAS_TAG_NAME
// CLOSETAG            '>' ->  FINISHED
            case TC_CLOSETAG:
                bCloseTag = sal_True;
                switch ( cDelim )
                {
                    case ' ': aState = TC_CLOSETAG_HAS_TAG_NAME;
                               aTagName = aPortion;
                               bCheckName = sal_True;
                               break;
                    case '>': aState = TC_FINISHED;
                               aTagName = aPortion;
                               bCheckName = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;

// CLOSETAG_HAS_TAG_NAME       '>' ->  FINISHED
            case TC_CLOSETAG_HAS_TAG_NAME:
                switch ( cDelim )
                {
                    case '>': aState = TC_FINISHED;
                               bCheckEmpty = sal_True;
                               break;
                    default:   aState = TC_ERROR;
                }
                break;


            default: rErrorList.AddError( 99, "Internal error Parsing Tag ", *this );
                     bIsBroken = sal_True;

        }

        if ( bCheckName )
        {
            if ( aPortion.Len() == 0 )
            {
                rErrorList.AddError( 25, "Tag/Property name missing ", *this );
                bIsBroken = sal_True;
            }
            else
            {
                aName = ByteString( aPortion, RTL_TEXTENCODING_UTF8 );
                // "a-zA-Z_-.0-9" 
                xub_StrLen nCount;
                sal_Bool bBroken = sal_False;
                const sal_Char* aBuf = aName.GetBuffer();
                for ( nCount = 0 ; !bBroken && nCount < aName.Len() ; nCount++ )
                {
                    bBroken = ! (   ( aBuf[nCount] >= 'a' && aBuf[nCount] <= 'z' )
                                ||( aBuf[nCount] >= 'A' && aBuf[nCount] <= 'Z' )
                                ||( aBuf[nCount] >= '0' && aBuf[nCount] <= '9' )
                                ||( aBuf[nCount] == '_' )
                                ||( aBuf[nCount] == '-' )
                                ||( aBuf[nCount] == '.' )
                                );
                }

                if ( bBroken )
                {
                    rErrorList.AddError( 25, "Found illegal character in Tag/Property name ", *this );
                    bIsBroken = sal_True;
                }
            }

            bCheckName = sal_False;
        }

        if ( bCheckEmpty )
        {
            if ( aPortion.Len() )
            {
                rErrorList.AddError( 25, ByteString("Found displaced characters '").Append(ByteString( aPortion, RTL_TEXTENCODING_UTF8 )).Append("' in Tag "), *this );
                bIsBroken = sal_True;
            }
            bCheckEmpty = sal_False;
        }


        nLastPos = nCheckPos;

        // skip further blanks
        if ( cDelim == ' ' && aState != TC_INSIDE_STRING )
            while ( nLastPos < aTokenString.Len() && aTokenString.GetChar( nLastPos ) == ' ')
                nLastPos++;

        nCheckPos = aTokenString.SearchChar( aDelims.GetBuffer(), nLastPos );
    }
    if ( aState != TC_FINISHED )
    {
        rErrorList.AddError( 25, "Parsing error in Tag ", *this );
        bIsBroken = sal_True;
    }
}

sal_Bool TokenInfo::IsPropertyRelevant( const ByteString &aName, const String &aValue ) const
{
    if ( aTagName.EqualsAscii( "alt" ) && aName.Equals( "xml-lang" ) )
        return sal_False;
    if ( aTagName.EqualsAscii( "ahelp" ) && aName.Equals( "visibility" ) && aValue.EqualsAscii("visible") )
        return sal_False;
    if ( aTagName.EqualsAscii( "image" ) && (aName.Equals( "width" ) || aName.Equals( "height" )) )
        return sal_False;
    
    return sal_True;
}

sal_Bool TokenInfo::IsPropertyValueValid( const ByteString &aName, const String &aValue ) const
{
/*  removed due to i56740
    if ( aTagName.EqualsAscii( "switchinline" ) && aName.Equals( "select" ) )
    {
        return aValue.EqualsAscii("sys") ||
               aValue.EqualsAscii("appl") ||
               aValue.EqualsAscii("distrib");
    } */
    if ( aTagName.EqualsAscii( "caseinline" ) && aName.Equals( "select" ) )
    {
        return /*!aValue.EqualsAscii("OS2") &&  removed due to i56740 */
               !aValue.EqualsAscii("");
    }

    // we don't know any better so we assume it to be OK
    return sal_True;
}

sal_Bool TokenInfo::IsPropertyInvariant( const ByteString &aName, const String &aValue ) const
{
    if ( aTagName.EqualsAscii( "link" ) && aName.Equals( "name" ) )
        return sal_False;
    if ( aTagName.EqualsAscii( "link" ) && aName.Equals( "href" ) )
    {   // check for external reference
        if (  aValue.Copy( 0, 5 ).EqualsIgnoreCaseAscii( "http:" )
           || aValue.Copy( 0, 6 ).EqualsIgnoreCaseAscii( "https:" )
           || aValue.Copy( 0, 4 ).EqualsIgnoreCaseAscii( "ftp:" ) )
            return sal_False;
        else
            return sal_True;
    }
    return sal_True;
}

sal_Bool TokenInfo::IsPropertyFixable( const ByteString &aName ) const
{
    // name everything that is allowed to be fixed automatically here
    if ( (aTagName.EqualsAscii( "ahelp" ) && aName.Equals( "hid" )) 
      || (aTagName.EqualsAscii( "link" ) && aName.Equals( "href" )) 
      || (aTagName.EqualsAscii( "alt" ) && aName.Equals( "id" )) 
      || (aTagName.EqualsAscii( "variable" ) && aName.Equals( "id" )) 
      || (aTagName.EqualsAscii( "image" ) && aName.Equals( "src" ))
      || (aTagName.EqualsAscii( "image" ) && aName.Equals( "id" ) ))
        return sal_True;
    return sal_False;
}

sal_Bool TokenInfo::MatchesTranslation( TokenInfo& rInfo, sal_Bool bGenErrors, ParserMessageList &rErrorList, sal_Bool bFixTags ) const
{
    // check if tags are equal
    // check if all existing properties are in the translation as well and
    // wether they have a matching content (the same in most cases)

    if ( nId != rInfo.nId )
        return sal_False;

    if ( !aTagName.Equals( rInfo.aTagName ) )
        return sal_False;

    // If one of the tags has formating errors already it does make no sense to check here, so return right away
    if ( bGenErrors && ( bIsBroken || rInfo.bIsBroken ) )
        return sal_True;

	StringHashMap::const_iterator iProp;
	for( iProp = aProperties.begin() ; iProp != aProperties.end(); ++iProp )
    {
        if ( rInfo.aProperties.find( iProp->first ) != rInfo.aProperties.end() )
        {
            if ( IsPropertyRelevant( iProp->first, iProp->second ) || IsPropertyRelevant( iProp->first, rInfo.aProperties.find( iProp->first )->second ) )
            {
                if ( IsPropertyInvariant( iProp->first, iProp->second ) )
                {
                    if ( !rInfo.aProperties.find( iProp->first )->second.Equals( iProp->second ) )
                    {
                        if ( bGenErrors )
                        {
                            if ( bFixTags && IsPropertyFixable( iProp->first ) )
                            {
                                rInfo.aProperties.find( iProp->first )->second = iProp->second;
                                rInfo.SetHasBeenFixed();
                                rErrorList.AddWarning( 25, ByteString("Property '").Append(iProp->first).Append("': FIXED different value in Translation "), *this );
                            }
                            else
                                rErrorList.AddError( 25, ByteString("Property '").Append(iProp->first).Append("': value different in Translation "), *this );
                        }
                        else return sal_False;
                    }
                }
            }
        }
        else
        {
            if ( IsPropertyRelevant( iProp->first, iProp->second ) )
            {
                if ( bGenErrors )
                    rErrorList.AddError( 25, ByteString("Property '").Append(iProp->first).Append("' missing in Translation "), *this );
                else return sal_False;
            }
        }
	}
	for( iProp = rInfo.aProperties.begin() ; iProp != rInfo.aProperties.end(); ++iProp )
    {
        if ( aProperties.find( iProp->first ) == aProperties.end() )
        {
            if ( IsPropertyRelevant( iProp->first, iProp->second ) )
            {
                if ( bGenErrors )
                    rErrorList.AddError( 25, ByteString("Extra Property '").Append(iProp->first).Append("' in Translation "), rInfo );
                else return sal_False;
            }
        }
	}

    // if we reach here eather 
    //   the tags match completely or
    //   the tags match but not the properties and we generated errors for that
    return sal_True;
}

String TokenInfo::GetTagName() const
{
    return aTagName;
}

String TokenInfo::MakeTag() const
{
    String aRet;
    aRet.AppendAscii("\\<");
    if ( bCloseTag )
        aRet.AppendAscii("/");
    aRet.Append( GetTagName() );
	StringHashMap::const_iterator iProp;

	for( iProp = aProperties.begin() ; iProp != aProperties.end(); ++iProp )
    {
        aRet.AppendAscii(" ");
        aRet.Append( String( iProp->first, RTL_TEXTENCODING_UTF8 ) );
        aRet.AppendAscii("=\\\"");
        aRet.Append( iProp->second );
        aRet.AppendAscii("\\\"");
    }
    if ( bClosed )
        aRet.AppendAscii("/");
    aRet.AppendAscii("\\>");
    return aRet;
}


void ParserMessageList::AddError( sal_uInt16 nErrorNr, ByteString aErrorText, const TokenInfo &rTag )
{
    Insert( new ParserError( nErrorNr, aErrorText, rTag ), LIST_APPEND );
}

void ParserMessageList::AddWarning( sal_uInt16 nErrorNr, ByteString aErrorText, const TokenInfo &rTag )
{
    Insert( new ParserWarning( nErrorNr, aErrorText, rTag ), LIST_APPEND );
}

sal_Bool ParserMessageList::HasErrors()
{
    sal_uInt16 i;
    for ( i=0 ; i < Count() ; i++ )
        if ( GetObject( i )->IsError() )
            return sal_True;
    return sal_False;
}

struct Tag
{
    String GetName() const { return String::CreateFromAscii( pName ); };
	const char* pName;
	TokenId nTag;
};


static const Tag aKnownTags[] =
{
/*  commenting oldstyle tags
//	{ "<#GROUP_FORMAT>", TAG_GROUP_FORMAT },
	{ "<#BOLD>", TAG_BOLDON },
	{ "<#/BOLD>", TAG_BOLDOFF },
	{ "<#ITALIC>", TAG_ITALICON },
	{ "<#/ITALIC>", TAG_ITALICOFF },
	{ "<#UNDER>", TAG_UNDERLINEON },
	{ "<#/UNDER>", TAG_UNDERLINEOFF },

//	{ "<#GROUP_NOTALLOWED>", TAG_GROUP_NOTALLOWED },
	{ "<#HELPID>", TAG_HELPID },
	{ "<#MODIFY>", TAG_MODIFY },
	{ "<#REFNR>", TAG_REFNR },

//	{ "<#GROUP_STRUCTURE>", TAG_GROUP_STRUCTURE },
	{ "<#NAME>", TAG_NAME },
	{ "<#HREF>", TAG_HREF },
	{ "<#AVIS>", TAG_AVIS },
	{ "<#AHID>", TAG_AHID },
	{ "<#AEND>", TAG_AEND },

	{ "<#TITEL>", TAG_TITEL },
	{ "<#KEY>", TAG_KEY },
	{ "<#INDEX>", TAG_INDEX },

	{ "<#REFSTART>", TAG_REFSTART },

	{ "<#GRAPHIC>", TAG_GRAPHIC },
	{ "<#NEXTVERSION>", TAG_NEXTVERSION },

    //	{ "<#GROUP_SYSSWITCH>", TAG_GROUP_SYSSWITCH },
	{ "<#WIN>", TAG_WIN },
	{ "<#UNIX>", TAG_UNIX },
	{ "<#MAC>", TAG_MAC },
	{ "<#OS2>", TAG_OS2 },

//	{ "<#GROUP_PROGSWITCH>", TAG_GROUP_PROGSWITCH },
	{ "<#WRITER>", TAG_WRITER },
	{ "<#CALC>", TAG_CALC },
	{ "<#DRAW>", TAG_DRAW },
	{ "<#IMPRESS>", TAG_IMPRESS },
	{ "<#SCHEDULE>", TAG_SCHEDULE },
	{ "<#IMAGE>", TAG_IMAGE },
	{ "<#MATH>", TAG_MATH },
	{ "<#CHART>", TAG_CHART },
	{ "<#OFFICE>", TAG_OFFICE },
  */
//	{ "<#TAG_GROUP_META>", TAG_GROUP_META },
	{ "$[officefullname]", TAG_OFFICEFULLNAME },
	{ "$[officename]", TAG_OFFICENAME },
	{ "$[officepath]", TAG_OFFICEPATH },
	{ "$[officeversion]", TAG_OFFICEVERSION },
	{ "$[portalname]", TAG_PORTALNAME },
	{ "$[portalfullname]", TAG_PORTALFULLNAME },
	{ "$[portalpath]", TAG_PORTALPATH },
	{ "$[portalversion]", TAG_PORTALVERSION },
	{ "$[portalshortname]", TAG_PORTALSHORTNAME },
/*  commenting oldstyle tags
//	{ "<#TAG_GROUP_SINGLE>", TAG_GROUP_SINGLE },
	{ "<#REFINSERT>", TAG_REFINSERT },

//	{ "<#GROUP_MULTI>", TAG_GROUP_MULTI },
	{ "<#END>", TAG_END },
	{ "<#ELSE>", TAG_ELSE },
	{ "<#VERSIONEND>", TAG_VERSIONEND },
	{ "<#ENDGRAPHIC>", TAG_ENDGRAPHIC },*/
	{ "<Common Tag>", TAG_COMMONSTART },
	{ "</Common Tag>", TAG_COMMONEND },

    { "<no more tags>", TAG_NOMORETAGS },
	{ "", TAG_UNKNOWN_TAG },
};


SimpleParser::SimpleParser()
: nPos( 0 )
, aNextTag( TAG_NOMORETAGS, TOK_INVALIDPOS )
{
}

void SimpleParser::Parse( String PaSource )
{
    aSource = PaSource;
	nPos = 0;
	aLastToken.Erase();
    aNextTag = TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );
	aTokenList.Clear();
};

TokenInfo SimpleParser::GetNextToken( ParserMessageList &rErrorList )
{
    TokenInfo aResult;
    sal_uInt16 nTokenStartPos = 0;
    if ( aNextTag.nId != TAG_NOMORETAGS )
    {
        aResult = aNextTag;
        aNextTag = TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );
    }
    else
    {
	    aLastToken = GetNextTokenString( rErrorList, nTokenStartPos );
	    if ( aLastToken.Len() == 0 )
		    return TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );

        // do we have a \< ... \> style tag?
        if ( aLastToken.Copy(0,2).EqualsAscii( "\\<" ) )
        {
            // check for paired \" \"
            bool bEven = true;
            sal_uInt16 nQuotePos = 0;
    	    sal_uInt16 nQuotedQuotesPos = aLastToken.SearchAscii( "\\\"" );
    	    sal_uInt16 nQuotedBackPos = aLastToken.SearchAscii( "\\\\" );    // this is only to kick out quoted backslashes
            while ( nQuotedQuotesPos != STRING_NOTFOUND )
            {
                if ( nQuotedBackPos <= nQuotedQuotesPos )
                    nQuotePos = nQuotedBackPos+2;
                else
                {
                    nQuotePos = nQuotedQuotesPos+2;
                    bEven = !bEven;
                }
		        nQuotedQuotesPos = aLastToken.SearchAscii( "\\\"", nQuotePos );
    	        nQuotedBackPos = aLastToken.SearchAscii( "\\\\", nQuotePos );    // this is only to kick out quoted backslashes
            }
            if ( !bEven )
            {
        		rErrorList.AddError( 24, "Missing quotes ( \\\" ) in Tag", TokenInfo( TAG_UNKNOWN_TAG, nTokenStartPos, aLastToken ) );
            }

            // check if we have an end-tag or a start-tag
            sal_uInt16 nNonBlankStartPos,nNonBlankEndPos;
            nNonBlankStartPos = 2;
            while ( aLastToken.GetChar(nNonBlankStartPos) == ' ' )
                nNonBlankStartPos++;
            if ( aLastToken.GetChar(nNonBlankStartPos) == '/' )
                aResult = TokenInfo( TAG_COMMONEND, nTokenStartPos, aLastToken, rErrorList );
            else
            {
                aResult = TokenInfo( TAG_COMMONSTART, nTokenStartPos, aLastToken, rErrorList );
                nNonBlankEndPos = aLastToken.Len() -3;
                while ( aLastToken.GetChar(nNonBlankEndPos) == ' ' )
                    nNonBlankEndPos--;
                if ( aLastToken.GetChar( nNonBlankEndPos ) == '/' )
                    aNextTag = TokenInfo( TAG_COMMONEND, nTokenStartPos, String::CreateFromAscii("\\</").Append(aResult.GetTagName()).AppendAscii("\\>"), rErrorList );
            }
        }
        else
        {
	        sal_uInt16 i = 0;
	        while ( aKnownTags[i].nTag != TAG_UNKNOWN_TAG &&
		        aLastToken != aKnownTags[i].GetName() )
		        i++;
            aResult = TokenInfo( aKnownTags[i].nTag, nTokenStartPos );
        }
    }

    if ( aResult.nId == TAG_UNKNOWN_TAG )
        aResult = TokenInfo( TAG_UNKNOWN_TAG, nTokenStartPos, aLastToken );
	aTokenList.Insert( aResult, LIST_APPEND );
	return aResult;
}

String SimpleParser::GetNextTokenString( ParserMessageList &rErrorList, sal_uInt16 &rTagStartPos )
{
//	sal_uInt16 nStyle1StartPos = aSource.SearchAscii( "<#", nPos );
	sal_uInt16 nStyle2StartPos = aSource.SearchAscii( "$[", nPos );
	sal_uInt16 nStyle3StartPos = aSource.SearchAscii( "\\<", nPos );
	sal_uInt16 nStyle4StartPos = aSource.SearchAscii( "\\\\", nPos );    // this is only to kick out quoted backslashes

    rTagStartPos = 0;

/* removing since a \<... is not likely
    // check if the tag starts with a letter to avoid things like <> <= ... >
    while ( STRING_NOTFOUND != nStyle3StartPos && !( aSource.Copy( nStyle3StartPos+2, 1 ).IsAlphaAscii() || aSource.GetChar( nStyle3StartPos+2 ) == '/' ) )
    	nStyle3StartPos = aSource.SearchAscii( "\\<", nStyle3StartPos+1 );
*/
    if ( STRING_NOTFOUND == nStyle2StartPos && STRING_NOTFOUND == nStyle3StartPos )
        return String();  // no more tokens

	if ( nStyle4StartPos < nStyle2StartPos && nStyle4StartPos <= nStyle3StartPos )  // <= to make sure \\ is always handled first
    {   // Skip quoted Backslash
        nPos = nStyle4StartPos +2;
        return GetNextTokenString( rErrorList, rTagStartPos );
    }

/*	if ( nStyle1StartPos < nStyle2StartPos && nStyle1StartPos <= nStyle3StartPos )  // <= to make sure our spechial tags are recognized before all others
    {	// test for <# ... > style tokens
	    sal_uInt16 nEndPos = aSource.SearchAscii( ">", nStyle1StartPos );
        if ( nEndPos == STRING_NOTFOUND )
        {   // Token is incomplete. Skip start and search for better ones
            nPos = nStyle1StartPos +2;
            return GetNextTokenString( rErrorList, rTagStartPos );
        }
	    nPos = nEndPos;
        rTagStartPos = nStyle1StartPos;
	    return aSource.Copy( nStyle1StartPos, nEndPos-nStyle1StartPos +1 ).ToUpperAscii();
    }
    else*/ if ( nStyle2StartPos < nStyle3StartPos )
	{	// test for $[ ... ] style tokens
		sal_uInt16 nEndPos = aSource.SearchAscii( "]", nStyle2StartPos);
        if ( nEndPos == STRING_NOTFOUND )
        {   // Token is incomplete. Skip start and search for better ones
            nPos = nStyle2StartPos +2;
            return GetNextTokenString( rErrorList, rTagStartPos );
        }
		nPos = nEndPos;
        rTagStartPos = nStyle2StartPos;
		return aSource.Copy( nStyle2StartPos, nEndPos-nStyle2StartPos +1 );
	}
    else
	{	// test for \< ... \> style tokens
    	sal_uInt16 nEndPos = aSource.SearchAscii( "\\>", nStyle3StartPos);
    	sal_uInt16 nQuotedBackPos = aSource.SearchAscii( "\\\\", nStyle3StartPos );    // this is only to kick out quoted backslashes
        while ( nQuotedBackPos <= nEndPos && nQuotedBackPos != STRING_NOTFOUND )
        {
		    nEndPos = aSource.SearchAscii( "\\>", nQuotedBackPos +2);
    	    nQuotedBackPos = aSource.SearchAscii( "\\\\", nQuotedBackPos +2 );    // this is only to kick out quoted backslashes
        }
        if ( nEndPos == STRING_NOTFOUND )
        {   // Token is incomplete. Skip start and search for better ones
            nPos = nStyle3StartPos +2;
		    ByteString sTmp( "Tag Start '\\<' without Tag End '\\>': " );
        	rErrorList.AddError( 24, "Tag Start '\\<' without Tag End '\\>'", TokenInfo( TAG_UNKNOWN_TAG, nStyle3StartPos, aSource.Copy( nStyle3StartPos-10, 20 ) ) );
            return GetNextTokenString( rErrorList, rTagStartPos );
        }
        // check for paired quoted "    -->   \"sometext\"

        nPos = nEndPos;
        rTagStartPos = nStyle3StartPos;
		return aSource.Copy( nStyle3StartPos, nEndPos-nStyle3StartPos +2 );
	}
}

String SimpleParser::GetLexem( TokenInfo const &aToken )
{
    if ( aToken.aTokenString.Len() )
        return aToken.aTokenString;
    else
    {
        sal_uInt16 i = 0;
	    while ( aKnownTags[i].nTag != TAG_UNKNOWN_TAG &&
		    aKnownTags[i].nTag != aToken.nId )
		    i++;

        return aKnownTags[i].GetName();
    }
}

TokenParser::TokenParser()
: pErrorList( NULL )
{}

void TokenParser::Parse( const String &aCode, ParserMessageList* pList )
{
    pErrorList = pList;

	//Scanner initialisieren
	aParser.Parse( aCode );

	//erstes Symbol holen
	aTag = aParser.GetNextToken( *pErrorList );

	nPfCaseOptions = 0;
	nAppCaseOptions = 0;
	bPfCaseActive = sal_False;
	bAppCaseActive = sal_False;

	nActiveRefTypes = 0;

    //Ausfuehren der Start-Produktion
	Paragraph();

	//Es wurde nicht die ganze Kette abgearbeitet, bisher ist aber
	//kein Fehler aufgetreten
	//=> es wurde ein einleitendes Tag vergessen
	if ( aTag.nId != TAG_NOMORETAGS )
	{
		switch ( aTag.nId )
		{
			case TAG_END:
				{
					ParseError( 3, "Extra Tag <#END>. Switch or <#HREF> expected.", aTag );
				}
				break;
			case TAG_BOLDOFF:
				{
					ParseError( 4, "<#BOLD> expected before <#/BOLD>.", aTag );
				}
				break;
			case TAG_ITALICOFF:
				{
					ParseError( 5, "<#ITALIC> expected before <#/ITALIC>.", aTag );
				}
				break;
			case TAG_UNDERLINEOFF:
				{
					ParseError( 17, "<#UNDER> expected before <#/UNDER>.", aTag );
				}
				break;
/*			case TAG_MISSPARENTHESIS:
				{
					ParseError( 14, "missing closing parenthesis '>'", aTag );
				}
				break;*/
			case TAG_AEND:
				{
					ParseError( 5, "Extra Tag <#AEND>. <#AVIS> or <#AHID> expected.", aTag );
				}
				break;
			case TAG_ELSE:
				{
					ParseError( 16, "Application-tag or platform-tag expected before <#ELSE>.", aTag );
				}
				break;
			case TAG_UNKNOWN_TAG:
				{
					ParseError( 6, "unknown Tag", aTag );
				}
				break;
			default:
				{
					ParseError( 6, "unexpected Tag", aTag );
				}
		}
	}
    pErrorList = NULL;
}

void TokenParser::Paragraph()
{
	switch ( aTag.nId )
	{
		case TAG_GRAPHIC:
		case TAG_NEXTVERSION:
			{
				TagRef();
				Paragraph();
			}
			break;
		case TAG_AVIS:
		case TAG_AHID:
			{
				TagRef();
				Paragraph();
			}
			break;
		case TAG_HELPID:
			{
				SimpleTag();
				Paragraph();
			}
			break;
		case TAG_OFFICEFULLNAME:
		case TAG_OFFICENAME:
		case TAG_OFFICEPATH:
		case TAG_OFFICEVERSION:
		case TAG_PORTALNAME:
		case TAG_PORTALFULLNAME:
		case TAG_PORTALPATH:
		case TAG_PORTALVERSION:
		case TAG_PORTALSHORTNAME:
			{
				SimpleTag();
				Paragraph();
			}
			break;
		case TAG_REFINSERT:
			{
				SimpleTag();
				Paragraph();
			}
			break;
		case TAG_BOLDON:
		case TAG_ITALICON:
		case TAG_UNDERLINEON:
		case TAG_COMMONSTART:
			{
				TagPair();
				Paragraph();
			}
			break;
		case TAG_HREF:
		case TAG_NAME:
		case TAG_KEY:
		case TAG_INDEX:
        case TAG_TITEL:
        case TAG_REFSTART:
            {
				TagRef();
				Paragraph();
			}
			break;
		case TAG_OS2:
		case TAG_WIN:
		case TAG_UNIX:
		case TAG_MAC: //...
			{
				if ( ! bPfCaseActive )
				{
                    //PfCases duerfen nicht verschachtelt sein:
					bPfCaseActive = sal_True;
					PfCase();

					//So jetzt kann wieder ein PfCase kommen:
					bPfCaseActive = sal_False;
					Paragraph();
				}
			}
			break;
		case TAG_WRITER:
		case TAG_CALC:
		case TAG_DRAW:
		case TAG_IMPRESS:
		case TAG_SCHEDULE:
		case TAG_IMAGE:
		case TAG_MATH:
		case TAG_CHART:
		case TAG_OFFICE:
			{
				if ( !bAppCaseActive )
				{
                    //AppCases duerfen nicht verschachtelt sein:
					bAppCaseActive = sal_True;
					AppCase();

                    //jetzt koennen wieder AppCases kommen:
					bAppCaseActive = sal_False;
					Paragraph();
				}
			}
			break;

		//Case TAG_BOLDOFF, TAG_ITALICOFF, TAG_BUNDERLINE, TAG_END
			//nichts tun wg. epsilon-Prod.
	}
}

void TokenParser::PfCase()
{

	//Produktion:
	//PfCase -> PfCaseBegin Paragraph (PfCase | PfCaseEnd)

	PfCaseBegin();

	//Jetzt ist eine PfCase-Produktion aktiv:
	Paragraph();
	switch ( aTag.nId )
	{
		case TAG_ELSE:
		case TAG_END:
			{
				CaseEnd();
			}
			break;
		case TAG_OS2:
		case TAG_WIN:
		case TAG_UNIX:
		case TAG_MAC: //First (PfBegin)
			{
				PfCase();
			}
			break;
		default:
			ParseError( 8, "<#ELSE> or <#END> or platform-tag expected.", aTag );
	}
    //Die gemerkten Tags wieder loeschen fuer naechstes PfCase:
	nPfCaseOptions = 0;
}

void TokenParser::PfCaseBegin()
{
	switch ( aTag.nId )
	{
		case TAG_OS2:
		case TAG_WIN:
		case TAG_UNIX:
		case TAG_MAC:
			{
				//Token darf noch nicht vorgekommen sein im
				//aktuellen Plattform-Case:
				if ( !HAS_FLAG( nPfCaseOptions, TAG_NOGROUP( aTag.nId ) ) )
				{
					SET_FLAG( nPfCaseOptions, TAG_NOGROUP( aTag.nId ) );
					match( aTag, aTag );
				}
				else {
					ParseError( 9, "Tag defined twice in the same platform-case", aTag );
				}
			}
	}
}

void TokenParser::AppCase()
{

	//Produktion:
	//AppCase -> AppCaseBegin Paragraph (AppCase | AppCaseEnd)


	AppCaseBegin();

	Paragraph();

	switch ( aTag.nId )
	{
		case TAG_ELSE:
		case TAG_END:
			{
				CaseEnd();
			}
			break;
		case TAG_WRITER:
		case TAG_DRAW:
		case TAG_CALC:
		case TAG_IMAGE:
		case TAG_MATH:
		case TAG_CHART:
		case TAG_OFFICE:
		case TAG_IMPRESS:
		case TAG_SCHEDULE: //First (AppBegin)
			{
				AppCase();
			}
			break;
		default:
			ParseError( 1, "<#ELSE> or <#END> or application-case-tag expected.", aTag );
		}

    //Die gemerkten Tags wieder loeschen fuer naechstes AppCase:
	nAppCaseOptions = 0;
}

void TokenParser::AppCaseBegin()
{
	switch ( aTag.nId )
	{
		case TAG_WRITER:
		case TAG_DRAW:
		case TAG_CALC:
		case TAG_IMAGE:
		case TAG_MATH:
		case TAG_CHART:
		case TAG_OFFICE:
		case TAG_IMPRESS:
		case TAG_SCHEDULE:
			{
				//Token darf noch nicht vorgekommen sein im
				//aktuellen Plattform-Case:
				if ( !HAS_FLAG( nAppCaseOptions, TAG_NOGROUP( aTag.nId ) ) )
				{
					SET_FLAG( nAppCaseOptions, TAG_NOGROUP( aTag.nId ) );
					match( aTag, aTag );
				}
				else {
					ParseError( 13, "Tag defined twice in the same application-case.", aTag );
				}
			}
	}
}

void TokenParser::CaseEnd()
{
	//Produktion:
	//CaseEnd -> <#ELSE> Paragraph <#END> | <#END>

	switch ( aTag.nId )
	{
		case TAG_ELSE:
		{
			match( aTag, TAG_ELSE );
			Paragraph();
			match( aTag, TAG_END );
		}
		break;
		case TAG_END:
		{
			match( aTag, TAG_END );
		}
		break;
		default:
			ParseError( 2, "<#ELSE> or <#END> expected.", aTag );
	}
}

void TokenParser::SimpleTag()
{

	switch ( aTag.nId )
	{
		case TAG_HELPID:
			{
				match( aTag, TAG_HELPID );
    		}
			break;
		case TAG_OFFICEFULLNAME:
		case TAG_OFFICENAME:
		case TAG_OFFICEPATH:
		case TAG_OFFICEVERSION:
		case TAG_PORTALNAME:
		case TAG_PORTALFULLNAME:
		case TAG_PORTALPATH:
		case TAG_PORTALVERSION:
		case TAG_PORTALSHORTNAME:

        case TAG_REFINSERT:
			{
				match( aTag, aTag );
    		}
			break;
		default:
			ParseError( 15, "[<#SimpleTag>] expected.", aTag );
	}
}

void TokenParser::TagPair()
{
	switch ( aTag.nId )
	{
		case TAG_BOLDON:
			{
				match( aTag, TAG_BOLDON );
				Paragraph();
				match( aTag, TAG_BOLDOFF );
			}
			break;
		case TAG_ITALICON:
			{
				match( aTag, TAG_ITALICON );
				Paragraph();
				match( aTag, TAG_ITALICOFF );
			}
			break;
		case TAG_UNDERLINEON:
			{
				match( aTag, TAG_UNDERLINEON );
				Paragraph();
				match( aTag, TAG_UNDERLINEOFF );
			}
			break;
		case TAG_COMMONSTART:
			{
                //remember tag so we can give the original tag in case of an error
                TokenInfo aEndTag( aTag );
                aEndTag.nId = TAG_COMMONEND;
				match( aTag, TAG_COMMONSTART );
				Paragraph();
				match( aTag, aEndTag );
			}
			break;
		default:
			ParseError( 10, "<#BOLD>, <#ITALIC>, <#UNDER> expected.", aTag );
	}
}


void TokenParser::TagRef()
{
	switch ( aTag.nId )
	{
		case TAG_GRAPHIC:
		case TAG_NEXTVERSION:
            {
				if ( !HAS_FLAG( nActiveRefTypes, TAG_NOGROUP( aTag.nId ) ) )
				{
					TokenId aThisToken = aTag.nId;
					SET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
					match( aTag, aTag );
					Paragraph();
                    if ( aThisToken == TAG_GRAPHIC )
    					match( aTag, TAG_ENDGRAPHIC );
                    else
    					match( aTag, TAG_VERSIONEND );
                    // don't reset since alowed only once per paragraph
					// RESET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
				}
				else
				{
					ParseError( 11, "Tags <#GRAPHIC>,<#NEXTVERSION> allowed only once per paragraph at", aTag );
				}
            }
			break;
		case TAG_AVIS:
		case TAG_AHID:
			{
				if ( !HAS_FLAG( nActiveRefTypes, TAG_NOGROUP( aTag.nId ) ) )
				{
					TokenId aThisToken = aTag.nId;
					SET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
					match( aTag, aTag );
					Paragraph();
					match( aTag, TAG_AEND );
					RESET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
				}
				else
				{
					ParseError( 11, "Nested <#AHID>,<#AVIS> not allowed.", aTag );
				}
			}
			break;
		case TAG_HREF:
		case TAG_NAME:
			{

			}
			// NOBREAK
		case TAG_KEY:
		case TAG_INDEX:
		case TAG_TITEL:
        case TAG_REFSTART:
			{
				if ( !HAS_FLAG( nActiveRefTypes, TAG_NOGROUP( aTag.nId ) ) )
				{
					TokenId aThisToken = aTag.nId;
					match( aTag, aTag );
					if ( aThisToken != TAG_NAME )
					{	// TAG_NAME has no TAG_END
						SET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
						Paragraph();
						match( aTag, TAG_END );
						RESET_FLAG( nActiveRefTypes, TAG_NOGROUP( aThisToken ) );
					}
				}
				else
				{
					ParseError( 11, "Nested <#HREF>,<#NAME> or <#KEY> not allowed.", aTag );
				}
			}
			break;
		default:
			ParseError( 12, "<#HREF>,<#NAME> or <#KEY> expected.", aTag );
	}
}

sal_Bool TokenParser::match( const TokenInfo &aCurrentToken, const TokenId &aExpectedToken )
{
    return match( aCurrentToken, TokenInfo( aExpectedToken, TOK_INVALIDPOS ) );
}

sal_Bool TokenParser::match( const TokenInfo &aCurrentToken, const TokenInfo &rExpectedToken )
{
    TokenInfo aExpectedToken( rExpectedToken );
	if ( aCurrentToken.nId == aExpectedToken.nId )
	{
        if ( ( aCurrentToken.nId == TAG_COMMONEND
               && aCurrentToken.GetTagName().Equals( aExpectedToken.GetTagName() ) )
             || aCurrentToken.nId != TAG_COMMONEND )
        {
		    aTag = aParser.GetNextToken( *pErrorList );
		    return sal_True;
        }
	}

    if ( aExpectedToken.nId == TAG_COMMONEND )
    {
        aExpectedToken.aTokenString.Insert( String::CreateFromAscii( "Close tag for " ), 0 );
    }

    ByteString sTmp( "Expected Symbol" );
    if ( aCurrentToken.nId == TAG_NOMORETAGS )
    {
		ParseError( 7, sTmp, aExpectedToken );
    }
    else
    {
		sTmp += ": ";
		sTmp += ByteString( aParser.GetLexem( aExpectedToken ), RTL_TEXTENCODING_UTF8 );
		sTmp += " near ";
		ParseError( 7, sTmp, aCurrentToken );
    }
	return sal_False;
}

void TokenParser::ParseError( sal_uInt16 nErrNr, ByteString aErrMsg, const TokenInfo &rTag )
{
	pErrorList->AddError( nErrNr, aErrMsg, rTag);

    // Das Fehlerhafte Tag ueberspringen
	aTag = aParser.GetNextToken( *pErrorList );
}


ParserMessage::ParserMessage( sal_uInt16 PnErrorNr, ByteString PaErrorText, const TokenInfo &rTag )
		: nErrorNr( PnErrorNr )
		, aErrorText( PaErrorText )
		, nTagBegin( 0 )
		, nTagLength( 0 )
{
    String aLexem( SimpleParser::GetLexem( rTag ) );
    aErrorText.Append(": ");
	aErrorText += ByteString( aLexem, RTL_TEXTENCODING_UTF8 );
    if ( rTag.nId == TAG_NOMORETAGS )
        aErrorText.Append(" at end of line ");
    else if ( rTag.nPos != TOK_INVALIDPOS )
    {
        aErrorText.Append(" at Position ");
        aErrorText.Append( ByteString::CreateFromInt32( rTag.nPos ) );
    }
    nTagBegin = rTag.nPos;
	nTagLength = aLexem.Len();
}

ParserError::ParserError( sal_uInt16 ErrorNr, ByteString ErrorText, const TokenInfo &rTag )
: ParserMessage( ErrorNr, ErrorText, rTag )
{}

ParserWarning::ParserWarning( sal_uInt16 ErrorNr, ByteString ErrorText, const TokenInfo &rTag )
: ParserMessage( ErrorNr, ErrorText, rTag )
{}

sal_Bool LingTest::IsTagMandatory( TokenInfo const &aToken, TokenId &aMetaTokens )
{
	TokenId aTokenId = aToken.nId;
	TokenId aTokenGroup = TAG_GROUP( aTokenId );
	if ( TAG_GROUP_PROGSWITCH == aTokenGroup
		|| TAG_REFINSERT == aTokenId
		|| TAG_REFSTART == aTokenId
		|| TAG_NAME == aTokenId
		|| TAG_HREF == aTokenId
		|| TAG_AVIS == aTokenId
		|| TAG_AHID == aTokenId
		|| TAG_GRAPHIC == aTokenId
		|| TAG_NEXTVERSION == aTokenId
        || ( TAG_GROUP_META == aTokenGroup && (aMetaTokens & aTokenId) == aTokenId ) )
    {
        if ( TAG_GROUP_META == aTokenGroup )
            aMetaTokens |= aTokenId;
        return sal_True;
    }
    else if (   TAG_COMMONSTART == aTokenId
		     || TAG_COMMONEND == aTokenId )
    {
        String aTagName = aToken.GetTagName();
        return !(aTagName.EqualsIgnoreCaseAscii( "comment" )
              || aTagName.EqualsIgnoreCaseAscii( "bookmark_value" )
              || aTagName.EqualsIgnoreCaseAscii( "emph" )
              || aTagName.EqualsIgnoreCaseAscii( "item" )
              || aTagName.EqualsIgnoreCaseAscii( "br" ) );
    }
    return sal_False;
}

void LingTest::CheckTags( TokenList &aReference, TokenList &aTestee, sal_Bool bFixTags )
{
	sal_uLong i=0,j=0;
	// Clean old Warnings
	while ( aCompareWarningList.Count() )
	{
		delete aCompareWarningList.GetCurObject();
		aCompareWarningList.Remove();
	}

    /* in xml tags, do not require the following tags
        comment
        bookmark_value
        emph
        item
        br
    */

    // filter uninteresting Tags
    TokenId aMetaTokens = 0;
	for ( i=0 ; i < aReference.Count() ; i++ )
	{
		if ( !IsTagMandatory( aReference.GetObject( i ), aMetaTokens ) )
			aReference.GetObject( i ).SetDone();
	}

    aMetaTokens = 0;
	for ( i=0 ; i < aTestee.Count() ; i++ )
	{
		if ( !IsTagMandatory( aTestee.GetObject( i ), aMetaTokens ) )
			aTestee.GetObject( i ).SetDone();
	}

    // remove all matching tags
	for ( i=0 ; i < aReference.Count() ; i++ )
    {
        if ( aReference.GetObject( i ).IsDone() )
            continue;

    	sal_Bool bTagFound = sal_False;
    	for ( j=0 ; j < aTestee.Count() && !bTagFound ; j++ )
        {
            if ( aTestee.GetObject( j ).IsDone() )
                continue;

		    if ( aReference.GetObject( i ).MatchesTranslation( aTestee.GetObject( j ), sal_False, aCompareWarningList ) )
            {
			    aReference.GetObject( i ).SetDone();
			    aTestee.GetObject( j ).SetDone();
                bTagFound = sal_True;
            }
        }
    }

    sal_Bool bCanFix = sal_True;

    if ( bFixTags )
    {
        // we fix only if its a really simple case
        sal_uInt16 nTagCount = 0;
	    for ( i=0 ; i < aReference.Count() ; i++ )
            if ( !aReference.GetObject( i ).IsDone() )
                nTagCount++;
        if ( nTagCount > 1 )
            bCanFix = sal_False;

        nTagCount = 0;
	    for ( i=0 ; i < aTestee.Count() ; i++ )
            if ( !aTestee.GetObject( i ).IsDone() )
                nTagCount++;
        if ( nTagCount > 1 )
            bCanFix = sal_False;
    }

    // generate errors for tags that have differing attributes
	for ( i=0 ; i < aReference.Count() ; i++ )
    {
        if ( aReference.GetObject( i ).IsDone() )
            continue;

    	sal_Bool bTagFound = sal_False;
    	for ( j=0 ; j < aTestee.Count() && !bTagFound ; j++ )
        {
            if ( aTestee.GetObject( j ).IsDone() )
                continue;

		    if ( aReference.GetObject( i ).MatchesTranslation( aTestee.GetObject( j ), sal_True, aCompareWarningList, bCanFix && bFixTags ) )
            {
			    aReference.GetObject( i ).SetDone();
			    aTestee.GetObject( j ).SetDone();
                bTagFound = sal_True;
            }
        }
    }

    // list remaining tags as errors
	for ( i=0 ; i < aReference.Count() ; i++ )
    {
        if ( aReference.GetObject( i ).IsDone() )
            continue;

        aCompareWarningList.AddError( 20, "Missing Tag in Translation", aReference.GetObject( i ) );
    }
	for ( i=0 ; i < aTestee.Count() ; i++ )
    {
        if ( aTestee.GetObject( i ).IsDone() )
            continue;

		aCompareWarningList.AddError( 21, "Extra Tag in Translation", aTestee.GetObject( i ) );
    }

	for ( i=0 ; i < aReference.Count() ; i++ )
        aReference.GetObject( i ).SetDone( sal_False );

	for ( i=0 ; i < aTestee.Count() ; i++ )
        aTestee.GetObject( i ).SetDone( sal_False );
}

void LingTest::CheckReference( GSILine *aReference )
{
	aReferenceParser.Parse( aReference->GetUText(), aReference->GetMessageList() );
}

void LingTest::CheckTestee( GSILine *aTestee, sal_Bool bHasSourceLine, sal_Bool bFixTags )
{
    aFixedTestee = aTestee->GetUText();
	aTesteeParser.Parse( aFixedTestee, aTestee->GetMessageList() );

    if ( bHasSourceLine )
	    CheckTags( aReferenceParser.GetTokenList(), aTesteeParser.GetTokenList(), bFixTags );

    if ( bFixTags )
    {
        TokenList& aTesteeTokens = aTesteeParser.GetTokenList();
        sal_Bool bFixesDone = sal_False;
        // count backwards to allow replacing from right to left
        int i;
	    for ( i=aTesteeTokens.Count()-1 ; i>=0 ; i-- )
        {
            if ( aTesteeTokens.GetObject( i ).HasBeenFixed() )
            {
                bFixesDone = sal_True;
                aFixedTestee.Replace( aTesteeTokens.GetObject( i ).nPos, aTesteeTokens.GetObject( i ).aTokenString.Len(), aTesteeTokens.GetObject( i ).MakeTag() );
            }
        }
        if ( bFixesDone )
        {
            aTestee->SetUText( aFixedTestee );
            aTestee->SetFixed();
        }
    }
}

