1576 lines
52 KiB
C++
1576 lines
52 KiB
C++
/* -*- 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 "sal/config.h"
|
|
|
|
#include "rtl/strbuf.hxx"
|
|
#include "rtl/string.hxx"
|
|
#include "rtl/ustrbuf.hxx"
|
|
#include "rtl/ustring.hxx"
|
|
|
|
#include "tagtest.hxx"
|
|
|
|
#if OSL_DEBUG_LEVEL > 1
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#include "gsicheck.hxx"
|
|
#include "helper.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_Int32 nP, rtl::OUString const & 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_Int32 nLastPos = 2; // skip initial \<
|
|
sal_Int32 nCheckPos = nLastPos;
|
|
static char const aDelims[] = " \\=>/";
|
|
rtl::OUString aPortion;
|
|
rtl::OUString aValue; // store the value of a property
|
|
rtl::OString 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.getLength() && aTokenString[nLastPos] == ' ')
|
|
nLastPos++;
|
|
|
|
nCheckPos = helper::indexOfAnyAsciiL(
|
|
aTokenString, RTL_CONSTASCII_STRINGPARAM(aDelims), nLastPos);
|
|
while ( nCheckPos != -1 && !( aState == TC_FINISHED || aState == TC_ERROR ) )
|
|
{
|
|
aPortion = aTokenString.copy( nLastPos, nCheckPos-nLastPos );
|
|
|
|
if ( aTokenString[nCheckPos] == '\\' )
|
|
nCheckPos++;
|
|
|
|
cDelim = aTokenString[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.isEmpty())
|
|
{
|
|
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 = rtl::OUString();
|
|
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 = rtl::OUString();
|
|
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, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Property '")).append(aName).append(RTL_CONSTASCII_STRINGPARAM("' has invalid value '")).append(rtl::OUStringToOString(aValue, RTL_TEXTENCODING_UTF8)).append("' ").makeStringAndClear(), *this );
|
|
bIsBroken = sal_True;
|
|
}
|
|
aProperties[ aName ] = aValue;
|
|
}
|
|
else
|
|
{
|
|
rErrorList.AddError( 25, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Property '")).append(aName).append(RTL_CONSTASCII_STRINGPARAM("' defined twice ")).makeStringAndClear(), *this );
|
|
bIsBroken = sal_True;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
aState = TC_INSIDE_STRING;
|
|
aValue += aPortion;
|
|
aValue += rtl::OUString(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.isEmpty())
|
|
{
|
|
rErrorList.AddError( 25, "Tag/Property name missing ", *this );
|
|
bIsBroken = sal_True;
|
|
}
|
|
else
|
|
{
|
|
aName = rtl::OUStringToOString(aPortion, RTL_TEXTENCODING_UTF8);
|
|
// "a-zA-Z_-.0-9"
|
|
sal_Bool bBroken = sal_False;
|
|
const sal_Char* aBuf = aName.getStr();
|
|
for (sal_Int32 nCount = 0 ; !bBroken && nCount < aName.getLength() ; ++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.isEmpty())
|
|
{
|
|
rErrorList.AddError( 25, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Found displaced characters '")).append(rtl::OUStringToOString(aPortion, RTL_TEXTENCODING_UTF8)).append(RTL_CONSTASCII_STRINGPARAM("' in Tag ")).makeStringAndClear(), *this );
|
|
bIsBroken = sal_True;
|
|
}
|
|
bCheckEmpty = sal_False;
|
|
}
|
|
|
|
|
|
nLastPos = nCheckPos;
|
|
|
|
// skip further blanks
|
|
if ( cDelim == ' ' && aState != TC_INSIDE_STRING )
|
|
while ( nLastPos < aTokenString.getLength() && aTokenString[nLastPos] == ' ')
|
|
nLastPos++;
|
|
|
|
nCheckPos = helper::indexOfAnyAsciiL(
|
|
aTokenString, RTL_CONSTASCII_STRINGPARAM(aDelims), nLastPos);
|
|
}
|
|
if ( aState != TC_FINISHED )
|
|
{
|
|
rErrorList.AddError( 25, "Parsing error in Tag ", *this );
|
|
bIsBroken = sal_True;
|
|
}
|
|
}
|
|
|
|
sal_Bool TokenInfo::IsPropertyRelevant( const rtl::OString &rName, const rtl::OUString &rValue ) const
|
|
{
|
|
if ( aTagName == "alt" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("xml-lang")) )
|
|
return sal_False;
|
|
if ( aTagName == "ahelp" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("visibility")) && rValue == "visible" )
|
|
return sal_False;
|
|
if ( aTagName == "image" && (rName.equalsL(RTL_CONSTASCII_STRINGPARAM("width")) || rName.equalsL(RTL_CONSTASCII_STRINGPARAM("height"))) )
|
|
return sal_False;
|
|
|
|
return sal_True;
|
|
}
|
|
|
|
sal_Bool TokenInfo::IsPropertyValueValid( const rtl::OString &rName, const rtl::OUString &rValue ) const
|
|
{
|
|
/* removed due to i56740
|
|
if ( aTagName.EqualsAscii( "switchinline" ) && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("select")) )
|
|
{
|
|
return rValue.EqualsAscii("sys") ||
|
|
rValue.EqualsAscii("appl") ||
|
|
rValue.EqualsAscii("distrib");
|
|
} */
|
|
if ( aTagName == "caseinline" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("select")) )
|
|
{
|
|
return !rValue.isEmpty();
|
|
}
|
|
|
|
// we don't know any better so we assume it to be OK
|
|
return sal_True;
|
|
}
|
|
|
|
sal_Bool TokenInfo::IsPropertyInvariant( const rtl::OString &rName, const rtl::OUString &rValue ) const
|
|
{
|
|
if ( aTagName == "link" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("name")) )
|
|
return sal_False;
|
|
if ( aTagName == "link" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("href")) )
|
|
{ // check for external reference
|
|
return
|
|
!(rValue.matchIgnoreAsciiCaseAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("http:"))
|
|
|| rValue.matchIgnoreAsciiCaseAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("https:"))
|
|
|| rValue.matchIgnoreAsciiCaseAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("ftp:")));
|
|
}
|
|
return sal_True;
|
|
}
|
|
|
|
sal_Bool TokenInfo::IsPropertyFixable( const rtl::OString &rName ) const
|
|
{
|
|
// name everything that is allowed to be fixed automatically here
|
|
if ( (aTagName == "ahelp" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("hid")))
|
|
|| (aTagName == "link" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("href")))
|
|
|| (aTagName == "alt" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("id")))
|
|
|| (aTagName == "variable" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("id")))
|
|
|| (aTagName == "image" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("src")))
|
|
|| (aTagName == "image" && rName.equalsL(RTL_CONSTASCII_STRINGPARAM("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
|
|
// whether they have a matching content (the same in most cases)
|
|
|
|
if ( nId != rInfo.nId )
|
|
return sal_False;
|
|
|
|
if ( aTagName != 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 != iProp->second )
|
|
{
|
|
if ( bGenErrors )
|
|
{
|
|
if ( bFixTags && IsPropertyFixable( iProp->first ) )
|
|
{
|
|
rInfo.aProperties.find( iProp->first )->second = iProp->second;
|
|
rInfo.SetHasBeenFixed();
|
|
rErrorList.AddWarning( 25, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Property '")).append(iProp->first).append(RTL_CONSTASCII_STRINGPARAM("': FIXED different value in Translation ")).makeStringAndClear(), *this );
|
|
}
|
|
else
|
|
rErrorList.AddError( 25, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Property '")).append(iProp->first).append(RTL_CONSTASCII_STRINGPARAM("': value different in Translation ")).makeStringAndClear(), *this );
|
|
}
|
|
else return sal_False;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( IsPropertyRelevant( iProp->first, iProp->second ) )
|
|
{
|
|
if ( bGenErrors )
|
|
rErrorList.AddError( 25, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Property '")).append(iProp->first).append(RTL_CONSTASCII_STRINGPARAM("' missing in Translation ")).makeStringAndClear(), *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, rtl::OStringBuffer(RTL_CONSTASCII_STRINGPARAM("Extra Property '")).append(iProp->first).append(RTL_CONSTASCII_STRINGPARAM("' in Translation ")).makeStringAndClear(), 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;
|
|
}
|
|
|
|
rtl::OUString TokenInfo::GetTagName() const
|
|
{
|
|
return aTagName;
|
|
}
|
|
|
|
rtl::OUString TokenInfo::MakeTag() const
|
|
{
|
|
rtl::OUStringBuffer 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( rtl::OStringToOUString( iProp->first, RTL_TEXTENCODING_UTF8 ) );
|
|
aRet.appendAscii("=\\\"");
|
|
aRet.append( iProp->second );
|
|
aRet.appendAscii("\\\"");
|
|
}
|
|
if ( bClosed )
|
|
aRet.appendAscii("/");
|
|
aRet.appendAscii("\\>");
|
|
return aRet.makeStringAndClear();
|
|
}
|
|
|
|
|
|
void ParserMessageList::AddError( sal_Int32 nErrorNr, const rtl::OString& rErrorText, const TokenInfo &rTag )
|
|
{
|
|
maList.push_back( new ParserError( nErrorNr, rErrorText, rTag ) );
|
|
}
|
|
|
|
void ParserMessageList::AddWarning( sal_Int32 nErrorNr, const rtl::OString& rErrorText, const TokenInfo &rTag )
|
|
{
|
|
maList.push_back( new ParserWarning( nErrorNr, rErrorText, rTag ) );
|
|
}
|
|
|
|
sal_Bool ParserMessageList::HasErrors()
|
|
{
|
|
for ( size_t i = 0, n = maList.size(); i < n; ++i )
|
|
if ( maList[ i ]->IsError() )
|
|
return sal_True;
|
|
return sal_False;
|
|
}
|
|
|
|
void ParserMessageList::clear()
|
|
{
|
|
for ( size_t i = 0, n = maList.size(); i < n; ++i )
|
|
delete maList[ i ];
|
|
maList.clear();
|
|
}
|
|
|
|
struct Tag
|
|
{
|
|
rtl::OUString GetName() const { return rtl::OUString::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( rtl::OUString const & PaSource )
|
|
{
|
|
aSource = PaSource;
|
|
nPos = 0;
|
|
aLastToken = rtl::OUString();
|
|
aNextTag = TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );
|
|
aTokenList.clear();
|
|
};
|
|
|
|
TokenInfo SimpleParser::GetNextToken( ParserMessageList &rErrorList )
|
|
{
|
|
TokenInfo aResult;
|
|
sal_Int32 nTokenStartPos = 0;
|
|
if ( aNextTag.nId != TAG_NOMORETAGS )
|
|
{
|
|
aResult = aNextTag;
|
|
aNextTag = TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );
|
|
}
|
|
else
|
|
{
|
|
aLastToken = GetNextTokenString( rErrorList, nTokenStartPos );
|
|
if ( aLastToken.isEmpty() )
|
|
return TokenInfo( TAG_NOMORETAGS, TOK_INVALIDPOS );
|
|
|
|
// do we have a \< ... \> style tag?
|
|
if (aLastToken.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("\\<")))
|
|
{
|
|
// check for paired \" \"
|
|
bool bEven = true;
|
|
sal_Int32 nQuotePos = 0;
|
|
sal_Int32 nQuotedQuotesPos =
|
|
aLastToken.indexOfAsciiL(RTL_CONSTASCII_STRINGPARAM("\\\""));
|
|
sal_Int32 nQuotedBackPos = aLastToken.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\\"));
|
|
// this is only to kick out quoted backslashes
|
|
while (nQuotedQuotesPos != -1)
|
|
{
|
|
if ( nQuotedBackPos <= nQuotedQuotesPos )
|
|
nQuotePos = nQuotedBackPos+2;
|
|
else
|
|
{
|
|
nQuotePos = nQuotedQuotesPos+2;
|
|
bEven = !bEven;
|
|
}
|
|
nQuotedQuotesPos = aLastToken.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\""), nQuotePos);
|
|
nQuotedBackPos = aLastToken.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\\"), 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_Int32 nNonBlankStartPos = 2;
|
|
while (aLastToken[nNonBlankStartPos] == ' ')
|
|
nNonBlankStartPos++;
|
|
if (aLastToken[nNonBlankStartPos] == '/')
|
|
aResult = TokenInfo( TAG_COMMONEND, nTokenStartPos, aLastToken, rErrorList );
|
|
else
|
|
{
|
|
aResult = TokenInfo( TAG_COMMONSTART, nTokenStartPos, aLastToken, rErrorList );
|
|
sal_Int32 nNonBlankEndPos = aLastToken.getLength() - 3;
|
|
while (aLastToken[nNonBlankEndPos] == ' ')
|
|
nNonBlankEndPos--;
|
|
if (aLastToken[nNonBlankEndPos] == '/')
|
|
aNextTag = TokenInfo( TAG_COMMONEND, nTokenStartPos, rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("\\</")) + aResult.GetTagName() + rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("\\>")), rErrorList );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 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 );
|
|
return aResult;
|
|
}
|
|
|
|
rtl::OUString SimpleParser::GetNextTokenString( ParserMessageList &rErrorList, sal_Int32 &rTagStartPos )
|
|
{
|
|
sal_Int32 nStyle2StartPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("$["), nPos );
|
|
sal_Int32 nStyle3StartPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\<"), nPos);
|
|
sal_Int32 nStyle4StartPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\\"), nPos);
|
|
// this is only to kick out quoted backslashes
|
|
|
|
rTagStartPos = 0;
|
|
|
|
if (nStyle2StartPos == -1 && nStyle3StartPos == -1)
|
|
return rtl::OUString(); // 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 ( nStyle2StartPos < nStyle3StartPos )
|
|
{ // test for $[ ... ] style tokens
|
|
sal_Int32 nEndPos = aSource.indexOf(']', nStyle2StartPos);
|
|
if (nEndPos == -1)
|
|
{ // 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_Int32 nEndPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\>"), nStyle3StartPos);
|
|
sal_Int32 nQuotedBackPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\\"), nStyle3StartPos);
|
|
// this is only to kick out quoted backslashes
|
|
while (nQuotedBackPos <= nEndPos && nQuotedBackPos != -1)
|
|
{
|
|
nEndPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\>"), nQuotedBackPos + 2);
|
|
nQuotedBackPos = aSource.indexOfAsciiL(
|
|
RTL_CONSTASCII_STRINGPARAM("\\\\"), nQuotedBackPos + 2);
|
|
// this is only to kick out quoted backslashes
|
|
}
|
|
if (nEndPos == -1)
|
|
{ // Token is incomplete. Skip start and search for better ones
|
|
nPos = nStyle3StartPos +2;
|
|
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);
|
|
}
|
|
}
|
|
|
|
rtl::OUString SimpleParser::GetLexem( TokenInfo const &aToken )
|
|
{
|
|
if ( !aToken.aTokenString.isEmpty() )
|
|
return aToken.aTokenString;
|
|
else
|
|
{
|
|
sal_Int32 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 rtl::OUString &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_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_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_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_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() == aExpectedToken.GetTagName() )
|
|
|| aCurrentToken.nId != TAG_COMMONEND )
|
|
{
|
|
aTag = aParser.GetNextToken( *pErrorList );
|
|
return sal_True;
|
|
}
|
|
}
|
|
|
|
if ( aExpectedToken.nId == TAG_COMMONEND )
|
|
{
|
|
aExpectedToken.aTokenString =
|
|
rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Close tag for "))
|
|
+ aExpectedToken.aTokenString;
|
|
}
|
|
|
|
rtl::OString sTmp(RTL_CONSTASCII_STRINGPARAM("Expected Symbol"));
|
|
if ( aCurrentToken.nId == TAG_NOMORETAGS )
|
|
{
|
|
ParseError( 7, sTmp, aExpectedToken );
|
|
}
|
|
else
|
|
{
|
|
rtl::OStringBuffer aBuf(sTmp);
|
|
aBuf.append(": ").
|
|
append(rtl::OUStringToOString(aParser.GetLexem( aExpectedToken ), RTL_TEXTENCODING_UTF8)).
|
|
append(RTL_CONSTASCII_STRINGPARAM(" near "));
|
|
ParseError( 7, aBuf.makeStringAndClear(), aCurrentToken );
|
|
}
|
|
return sal_False;
|
|
}
|
|
|
|
void TokenParser::ParseError( sal_Int32 nErrNr, const rtl::OString &rErrMsg, const TokenInfo &rTag )
|
|
{
|
|
pErrorList->AddError( nErrNr, rErrMsg, rTag);
|
|
|
|
// Das Fehlerhafte Tag ueberspringen
|
|
aTag = aParser.GetNextToken( *pErrorList );
|
|
}
|
|
|
|
|
|
ParserMessage::ParserMessage( sal_Int32 PnErrorNr, const rtl::OString &rPaErrorText, const TokenInfo &rTag )
|
|
: nErrorNr( PnErrorNr )
|
|
, nTagBegin( 0 )
|
|
, nTagLength( 0 )
|
|
{
|
|
rtl::OUString aLexem( SimpleParser::GetLexem( rTag ) );
|
|
rtl::OStringBuffer aErrorBuffer(rPaErrorText);
|
|
aErrorBuffer.append(RTL_CONSTASCII_STRINGPARAM(": "));
|
|
aErrorBuffer.append(rtl::OUStringToOString(aLexem, RTL_TEXTENCODING_UTF8));
|
|
if ( rTag.nId == TAG_NOMORETAGS )
|
|
aErrorBuffer.append(RTL_CONSTASCII_STRINGPARAM(" at end of line "));
|
|
else if ( rTag.nPos != TOK_INVALIDPOS )
|
|
{
|
|
aErrorBuffer.append(RTL_CONSTASCII_STRINGPARAM(" at Position "));
|
|
aErrorBuffer.append(static_cast<sal_Int32>(rTag.nPos));
|
|
}
|
|
aErrorText = aErrorBuffer.makeStringAndClear();
|
|
nTagBegin = rTag.nPos;
|
|
nTagLength = aLexem.getLength();
|
|
}
|
|
|
|
ParserError::ParserError( sal_Int32 ErrorNr, const rtl::OString &rErrorText, const TokenInfo &rTag )
|
|
: ParserMessage( ErrorNr, rErrorText, rTag )
|
|
{}
|
|
|
|
ParserWarning::ParserWarning( sal_Int32 ErrorNr, const rtl::OString &rErrorText, const TokenInfo &rTag )
|
|
: ParserMessage( ErrorNr, rErrorText, 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 )
|
|
{
|
|
rtl::OUString aTagName = aToken.GetTagName();
|
|
return !(aTagName.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("comment"))
|
|
|| aTagName.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("bookmark_value"))
|
|
|| aTagName.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("emph"))
|
|
|| aTagName.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("item"))
|
|
|| aTagName.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("br")) );
|
|
}
|
|
return sal_False;
|
|
}
|
|
|
|
void LingTest::CheckTags( TokenList &aReference, TokenList &aTestee, sal_Bool bFixTags )
|
|
{
|
|
size_t i=0,j=0;
|
|
// Clean old Warnings
|
|
aCompareWarningList.clear();
|
|
|
|
/* 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.size() ; i++ )
|
|
{
|
|
if ( !IsTagMandatory( aReference[ i ], aMetaTokens ) )
|
|
aReference[ i ].SetDone();
|
|
}
|
|
|
|
aMetaTokens = 0;
|
|
for ( i=0 ; i < aTestee.size() ; i++ )
|
|
{
|
|
if ( !IsTagMandatory( aTestee[ i ], aMetaTokens ) )
|
|
aTestee[ i ].SetDone();
|
|
}
|
|
|
|
// remove all matching tags
|
|
for ( i=0 ; i < aReference.size() ; i++ )
|
|
{
|
|
if ( aReference[ i ].IsDone() )
|
|
continue;
|
|
|
|
sal_Bool bTagFound = sal_False;
|
|
for ( j=0 ; j < aTestee.size() && !bTagFound ; j++ )
|
|
{
|
|
if ( aTestee[ j ].IsDone() )
|
|
continue;
|
|
|
|
if ( aReference[ i ].MatchesTranslation( aTestee[ j ], sal_False, aCompareWarningList ) )
|
|
{
|
|
aReference[ i ].SetDone();
|
|
aTestee[ j ].SetDone();
|
|
bTagFound = sal_True;
|
|
}
|
|
}
|
|
}
|
|
|
|
sal_Bool bCanFix = sal_True;
|
|
|
|
if ( bFixTags )
|
|
{
|
|
// we fix only if its a really simple case
|
|
sal_Int32 nTagCount = 0;
|
|
for ( i=0 ; i < aReference.size() ; i++ )
|
|
if ( !aReference[ i ].IsDone() )
|
|
nTagCount++;
|
|
if ( nTagCount > 1 )
|
|
bCanFix = sal_False;
|
|
|
|
nTagCount = 0;
|
|
for ( i=0 ; i < aTestee.size() ; i++ )
|
|
if ( !aTestee[ i ].IsDone() )
|
|
nTagCount++;
|
|
if ( nTagCount > 1 )
|
|
bCanFix = sal_False;
|
|
}
|
|
|
|
// generate errors for tags that have differing attributes
|
|
for ( i=0 ; i < aReference.size() ; i++ )
|
|
{
|
|
if ( aReference[ i ].IsDone() )
|
|
continue;
|
|
|
|
sal_Bool bTagFound = sal_False;
|
|
for ( j=0 ; j < aTestee.size() && !bTagFound ; j++ )
|
|
{
|
|
if ( aTestee[ j ].IsDone() )
|
|
continue;
|
|
|
|
if ( aReference[ i ].MatchesTranslation( aTestee[ j ], sal_True, aCompareWarningList, bCanFix && bFixTags ) )
|
|
{
|
|
aReference[ i ].SetDone();
|
|
aTestee[ j ].SetDone();
|
|
bTagFound = sal_True;
|
|
}
|
|
}
|
|
}
|
|
|
|
// list remaining tags as errors
|
|
for ( i=0 ; i < aReference.size() ; i++ )
|
|
{
|
|
if ( aReference[ i ].IsDone() )
|
|
continue;
|
|
|
|
aCompareWarningList.AddError( 20, "Missing Tag in Translation", aReference[ i ] );
|
|
}
|
|
for ( i=0 ; i < aTestee.size() ; i++ )
|
|
{
|
|
if ( aTestee[ i ].IsDone() )
|
|
continue;
|
|
|
|
aCompareWarningList.AddError( 21, "Extra Tag in Translation", aTestee[ i ] );
|
|
}
|
|
|
|
for ( i=0 ; i < aReference.size() ; i++ )
|
|
aReference[ i ].SetDone( sal_False );
|
|
|
|
for ( i=0 ; i < aTestee.size() ; i++ )
|
|
aTestee[ 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.size() ; i > 0 ; )
|
|
{
|
|
if ( aTesteeTokens[ --i ].HasBeenFixed() )
|
|
{
|
|
bFixesDone = sal_True;
|
|
aFixedTestee = aFixedTestee.replaceAt( aTesteeTokens[ i ].nPos, aTesteeTokens[ i ].aTokenString.getLength(), aTesteeTokens[ i ].MakeTag() );
|
|
}
|
|
}
|
|
if ( bFixesDone )
|
|
{
|
|
aTestee->SetUText( aFixedTestee );
|
|
aTestee->SetFixed();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|