office-gobmx/l10ntools/source/cfgmerge.cxx
Stephan Bergmann 52a8d560d8 Extended loplugin:ostr: l10ntools
Change-Id: I259867d548c3a6b5322d38584270a325b93f1776
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160117
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
2023-11-30 09:00:00 +01:00

500 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* 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 .
*/
#include <sal/config.h>
#include <cfglex.hxx>
#include <common.hxx>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <rtl/strbuf.hxx>
#include <o3tl/string_view.hxx>
#include <helper.hxx>
#include <export.hxx>
#include <cfgmerge.hxx>
#include <utility>
#include <tokens.h>
namespace {
namespace global {
OString inputPathname;
std::unique_ptr< CfgParser > parser;
}
}
extern "C" {
FILE * init(int argc, char ** argv) {
common::HandledArgs aArgs;
if ( !common::handleArguments(argc, argv, aArgs) )
{
common::writeUsage("cfgex"_ostr,"*.xcu"_ostr);
std::exit(EXIT_FAILURE);
}
global::inputPathname = aArgs.m_sInputFile;
FILE * pFile = std::fopen(global::inputPathname.getStr(), "r");
if (pFile == nullptr) {
std::fprintf(
stderr, "Error: Cannot open file \"%s\"\n",
global::inputPathname.getStr() );
std::exit(EXIT_FAILURE);
}
if (aArgs.m_bMergeMode) {
global::parser.reset(
new CfgMerge(
aArgs.m_sMergeSrc, aArgs.m_sOutputFile,
global::inputPathname, aArgs.m_sLanguage ));
} else {
global::parser.reset(
new CfgExport(
aArgs.m_sOutputFile, global::inputPathname ));
}
return pFile;
}
void workOnTokenSet(int nTyp, char * pTokenText) {
global::parser->Execute( nTyp, pTokenText );
}
}
CfgStackData* CfgStack::Push(const OString &rTag, const OString &rId)
{
CfgStackData *pD = new CfgStackData( rTag, rId );
maList.push_back( pD );
return pD;
}
CfgStack::~CfgStack()
{
}
OString CfgStack::GetAccessPath( size_t nPos )
{
OStringBuffer sReturn;
for (size_t i = 0; i <= nPos; ++i)
{
if (i)
sReturn.append('.');
sReturn.append(maList[i]->GetIdentifier());
}
return sReturn.makeStringAndClear();
}
CfgStackData *CfgStack::GetStackData()
{
if (!maList.empty())
return maList[maList.size() - 1];
else
return nullptr;
}
CfgParser::CfgParser()
: pStackData( nullptr ),
bLocalize( false )
{
}
CfgParser::~CfgParser()
{
// CfgParser::ExecuteAnalyzedToken pushes onto aStack some XML entities (like XML and document
// type declarations) that don't have corresponding closing tags, so will never be popped off
// aStack again. But not pushing them onto aStack in the first place would change the
// identifiers computed in CfgStack::GetAccessPath, which could make the existing translation
// mechanisms fail. So, for simplicity, and short of more thorough input error checking, take
// into account here all the patterns of such declarations encountered during a build and during
// `make translations` (some inputs start with no such declarations at all, some inputs start
// with an XML declaration, and some inputs start with an XML declaration followed by a document
// type declaration) and pop any corresponding remaining excess elements off aStack:
if (aStack.size() == 2 && aStack.GetStackData()->GetTagType() == "!DOCTYPE") {
aStack.Pop();
}
if (aStack.size() == 1 && aStack.GetStackData()->GetTagType() == "?xml") {
aStack.Pop();
}
}
bool CfgParser::IsTokenClosed(std::string_view rToken)
{
return rToken[rToken.size() - 2] == '/';
}
void CfgParser::AddText(
OString &rText,
const OString &rIsoLang,
const OString &rResTyp )
{
rText = rText.replaceAll(OString('\n'), OString()).
replaceAll(OString('\r'), OString()).
replaceAll(OString('\t'), OString());
pStackData->sResTyp = rResTyp;
WorkOnText( rText, rIsoLang );
pStackData->sText[ rIsoLang ] = rText;
}
#if defined _MSC_VER
#pragma warning(disable: 4702) // unreachable code, bug in MSVC2015, it thinks the std::exit is unreachable
#endif
void CfgParser::ExecuteAnalyzedToken( int nToken, char *pToken )
{
OString sToken( pToken );
if ( sToken == " " || sToken == "\t" )
sLastWhitespace += sToken;
OString sTokenName;
bool bOutput = true;
switch ( nToken ) {
case CFG_TOKEN_PACKAGE:
case CFG_TOKEN_COMPONENT:
case CFG_TOKEN_TEMPLATE:
case CFG_TOKEN_CONFIGNAME:
case CFG_TOKEN_OORNAME:
case CFG_TOKEN_OORVALUE:
case CFG_TAG:
case ANYTOKEN:
case CFG_TEXT_START:
{
sTokenName = sToken.getToken(1, '<').getToken(0, '>').
getToken(0, ' ');
if ( !IsTokenClosed( sToken )) {
OString sSearch;
switch ( nToken ) {
case CFG_TOKEN_PACKAGE:
sSearch = "package-id="_ostr;
break;
case CFG_TOKEN_COMPONENT:
sSearch = "component-id="_ostr;
break;
case CFG_TOKEN_TEMPLATE:
sSearch = "template-id="_ostr;
break;
case CFG_TOKEN_CONFIGNAME:
sSearch = "cfg:name="_ostr;
break;
case CFG_TOKEN_OORNAME:
sSearch = "oor:name="_ostr;
bLocalize = true;
break;
case CFG_TOKEN_OORVALUE:
sSearch = "oor:value="_ostr;
break;
case CFG_TEXT_START: {
if ( sCurrentResTyp != sTokenName ) {
WorkOnResourceEnd();
}
sCurrentResTyp = sTokenName;
OString sTemp = sToken.copy( sToken.indexOf( "xml:lang=" ));
sCurrentIsoLang = sTemp.getToken(1, '"');
if ( sCurrentIsoLang == NO_TRANSLATE_ISO )
bLocalize = false;
pStackData->sTextTag = sToken;
sCurrentText = ""_ostr;
}
break;
}
OString sTokenId;
if ( !sSearch.isEmpty())
{
OString sTemp = sToken.copy( sToken.indexOf( sSearch ));
sTokenId = sTemp.getToken(1, '"');
}
pStackData = aStack.Push( sTokenName, sTokenId );
if ( sSearch == "cfg:name=" ) {
OString sTemp( sToken.toAsciiUpperCase() );
bLocalize = sTemp.indexOf("CFG:TYPE=\"STRING\"")>=0
&& sTemp.indexOf( "CFG:LOCALIZED=\"TRUE\"" )>=0;
}
}
else if ( sTokenName == "label" ) {
if ( sCurrentResTyp != sTokenName ) {
WorkOnResourceEnd();
}
sCurrentResTyp = sTokenName;
}
}
break;
case CFG_CLOSETAG:
{
sTokenName = sToken.getToken(1, '/').getToken(0, '>').
getToken(0, ' ');
if ( aStack.GetStackData() && ( aStack.GetStackData()->GetTagType() == sTokenName ))
{
if (sCurrentText.isEmpty())
WorkOnResourceEnd();
aStack.Pop();
pStackData = aStack.GetStackData();
}
else
{
const OString sError{ "Misplaced close tag: " + sToken + " in file " + global::inputPathname };
yyerror(sError.getStr());
std::exit(EXIT_FAILURE);
}
}
break;
case CFG_TEXTCHAR:
sCurrentText += sToken;
bOutput = false;
break;
case CFG_TOKEN_NO_TRANSLATE:
bLocalize = false;
break;
}
if ( !sCurrentText.isEmpty() && nToken != CFG_TEXTCHAR )
{
AddText( sCurrentText, sCurrentIsoLang, sCurrentResTyp );
Output( sCurrentText );
sCurrentText.clear();
pStackData->sEndTextTag = sToken;
}
if ( bOutput )
Output( sToken );
if ( sToken != " " && sToken != "\t" )
sLastWhitespace = ""_ostr;
}
void CfgExport::Output(const OString&)
{
}
void CfgParser::Execute( int nToken, char * pToken )
{
OString sToken( pToken );
switch ( nToken ) {
case CFG_TAG:
if ( sToken.indexOf( "package-id=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_PACKAGE, pToken );
return;
} else if ( sToken.indexOf( "component-id=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_COMPONENT, pToken );
return;
} else if ( sToken.indexOf( "template-id=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_TEMPLATE, pToken );
return;
} else if ( sToken.indexOf( "cfg:name=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_OORNAME, pToken );
return;
} else if ( sToken.indexOf( "oor:name=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_OORNAME, pToken );
return;
} else if ( sToken.indexOf( "oor:value=" ) != -1 ) {
ExecuteAnalyzedToken( CFG_TOKEN_OORVALUE, pToken );
return;
}
break;
}
ExecuteAnalyzedToken( nToken, pToken );
}
CfgExport::CfgExport(
const OString &rOutputFile,
OString sFilePath )
: sPath(std::move( sFilePath ))
{
pOutputStream.open( rOutputFile, PoOfstream::APP );
if (!pOutputStream.isOpen())
{
std::cerr << "ERROR: Unable to open output file: " << rOutputFile << "\n";
std::exit(EXIT_FAILURE);
}
}
CfgExport::~CfgExport()
{
pOutputStream.close();
}
void CfgExport::WorkOnResourceEnd()
{
if ( !bLocalize )
return;
if ( pStackData->sText["en-US"_ostr].isEmpty() )
return;
OString sXComment = pStackData->sText["x-comment"_ostr];
OString sLocalId = pStackData->sIdentifier;
OString sGroupId;
if ( aStack.size() == 1 ) {
sGroupId = sLocalId;
sLocalId = ""_ostr;
}
else {
sGroupId = aStack.GetAccessPath( aStack.size() - 2 );
}
OString sText = pStackData->sText[ "en-US"_ostr ];
sText = helper::UnQuotHTML( sText );
common::writePoEntry(
"Cfgex"_ostr, pOutputStream, sPath, pStackData->sResTyp,
sGroupId, sLocalId, sXComment, sText);
}
void CfgExport::WorkOnText(
OString &rText,
const OString &rIsoLang
)
{
if( !rIsoLang.isEmpty() ) rText = helper::UnQuotHTML( rText );
}
CfgMerge::CfgMerge(
const OString &rMergeSource, const OString &rOutputFile,
OString _sFilename, const OString &rLanguage )
: sFilename(std::move( _sFilename )),
bEnglish( false )
{
pOutputStream.open(
rOutputFile.getStr(), std::ios_base::out | std::ios_base::trunc);
if (!pOutputStream.is_open())
{
std::cerr << "ERROR: Unable to open output file: " << rOutputFile << "\n";
std::exit(EXIT_FAILURE);
}
if (!rMergeSource.isEmpty())
{
pMergeDataFile.reset(new MergeDataFile(
rMergeSource, global::inputPathname, true ));
if (rLanguage.equalsIgnoreAsciiCase("ALL") )
{
aLanguages = pMergeDataFile->GetLanguages();
}
else aLanguages.push_back(rLanguage);
}
else
aLanguages.push_back(rLanguage);
}
CfgMerge::~CfgMerge()
{
pOutputStream.close();
}
void CfgMerge::WorkOnText(OString &, const OString& rLangIndex)
{
if ( !(pMergeDataFile && bLocalize) )
return;
if ( !pResData ) {
OString sLocalId = pStackData->sIdentifier;
OString sGroupId;
if ( aStack.size() == 1 ) {
sGroupId = sLocalId;
sLocalId.clear();
}
else {
sGroupId = aStack.GetAccessPath( aStack.size() - 2 );
}
pResData.reset( new ResData( sGroupId, sFilename ) );
pResData->sId = sLocalId;
pResData->sResTyp = pStackData->sResTyp;
}
if (rLangIndex.equalsIgnoreAsciiCase("en-US"))
bEnglish = true;
}
void CfgMerge::Output(const OString& rOutput)
{
pOutputStream << rOutput;
}
void CfgMerge::WorkOnResourceEnd()
{
if ( pMergeDataFile && pResData && bLocalize && bEnglish ) {
MergeEntrys *pEntrys = pMergeDataFile->GetMergeEntrysCaseSensitive( pResData.get() );
if ( pEntrys ) {
OString sCur;
for( size_t i = 0; i < aLanguages.size(); ++i ){
sCur = aLanguages[ i ];
OString sContent;
pEntrys->GetText( sContent, sCur, true );
if (
( !sCur.equalsIgnoreAsciiCase("en-US") ) && !sContent.isEmpty())
{
OString sTextTag = pStackData->sTextTag;
const sal_Int32 nLangAttributeStart{ sTextTag.indexOf( "xml:lang=" ) };
const sal_Int32 nLangStart{ sTextTag.indexOf( '"', nLangAttributeStart )+1 };
const sal_Int32 nLangEnd{ sTextTag.indexOf( '"', nLangStart ) };
OString sAdditionalLine{ "\t"
+ sTextTag.replaceAt(nLangStart, nLangEnd-nLangStart, sCur)
+ helper::QuotHTML(sContent)
+ pStackData->sEndTextTag
+ "\n"
+ sLastWhitespace };
Output( sAdditionalLine );
}
}
}
}
pResData.reset();
bEnglish = false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */