1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
29 #include <rtl/strbuf.hxx>
30 #include <o3tl/string_view.hxx>
34 #include <cfgmerge.hxx>
42 OString inputPathname
;
43 std::unique_ptr
< CfgParser
> parser
;
50 FILE * init(int argc
, char ** argv
) {
52 common::HandledArgs aArgs
;
53 if ( !common::handleArguments(argc
, argv
, aArgs
) )
55 common::writeUsage("cfgex"_ostr
,"*.xcu"_ostr
);
56 std::exit(EXIT_FAILURE
);
58 global::inputPathname
= aArgs
.m_sInputFile
;
60 FILE * pFile
= std::fopen(global::inputPathname
.getStr(), "r");
61 if (pFile
== nullptr) {
63 stderr
, "Error: Cannot open file \"%s\"\n",
64 global::inputPathname
.getStr() );
65 std::exit(EXIT_FAILURE
);
68 if (aArgs
.m_bMergeMode
) {
71 aArgs
.m_sMergeSrc
, aArgs
.m_sOutputFile
,
72 global::inputPathname
, aArgs
.m_sLanguage
));
76 aArgs
.m_sOutputFile
, global::inputPathname
));
82 void workOnTokenSet(int nTyp
, char * pTokenText
) {
83 global::parser
->Execute( nTyp
, pTokenText
);
91 CfgStackData
* CfgStack::Push(const OString
&rTag
, const OString
&rId
)
93 CfgStackData
*pD
= new CfgStackData( rTag
, rId
);
94 maList
.push_back( pD
);
101 CfgStack::~CfgStack()
105 OString
CfgStack::GetAccessPath( size_t nPos
)
107 OStringBuffer sReturn
;
108 for (size_t i
= 0; i
<= nPos
; ++i
)
112 sReturn
.append(maList
[i
]->GetIdentifier());
115 return sReturn
.makeStringAndClear();
118 CfgStackData
*CfgStack::GetStackData()
121 return maList
[maList
.size() - 1];
129 CfgParser::CfgParser()
130 : pStackData( nullptr ),
135 CfgParser::~CfgParser()
137 // CfgParser::ExecuteAnalyzedToken pushes onto aStack some XML entities (like XML and document
138 // type declarations) that don't have corresponding closing tags, so will never be popped off
139 // aStack again. But not pushing them onto aStack in the first place would change the
140 // identifiers computed in CfgStack::GetAccessPath, which could make the existing translation
141 // mechanisms fail. So, for simplicity, and short of more thorough input error checking, take
142 // into account here all the patterns of such declarations encountered during a build and during
143 // `make translations` (some inputs start with no such declarations at all, some inputs start
144 // with an XML declaration, and some inputs start with an XML declaration followed by a document
145 // type declaration) and pop any corresponding remaining excess elements off aStack:
146 if (aStack
.size() == 2 && aStack
.GetStackData()->GetTagType() == "!DOCTYPE") {
149 if (aStack
.size() == 1 && aStack
.GetStackData()->GetTagType() == "?xml") {
154 bool CfgParser::IsTokenClosed(std::string_view rToken
)
156 return rToken
[rToken
.size() - 2] == '/';
159 void CfgParser::AddText(
161 const OString
&rIsoLang
,
162 const OString
&rResTyp
)
164 rText
= rText
.replaceAll(OString('\n'), OString()).
165 replaceAll(OString('\r'), OString()).
166 replaceAll(OString('\t'), OString());
167 pStackData
->sResTyp
= rResTyp
;
168 WorkOnText( rText
, rIsoLang
);
169 pStackData
->sText
[ rIsoLang
] = rText
;
173 #pragma warning(disable: 4702) // unreachable code, bug in MSVC2015, it thinks the std::exit is unreachable
175 void CfgParser::ExecuteAnalyzedToken( int nToken
, char *pToken
)
177 OString
sToken( pToken
);
179 if ( sToken
== " " || sToken
== "\t" )
180 sLastWhitespace
+= sToken
;
187 case CFG_TOKEN_PACKAGE
:
188 case CFG_TOKEN_COMPONENT
:
189 case CFG_TOKEN_TEMPLATE
:
190 case CFG_TOKEN_CONFIGNAME
:
191 case CFG_TOKEN_OORNAME
:
192 case CFG_TOKEN_OORVALUE
:
197 sTokenName
= sToken
.getToken(1, '<').getToken(0, '>').
200 if ( !IsTokenClosed( sToken
)) {
203 case CFG_TOKEN_PACKAGE
:
204 sSearch
= "package-id="_ostr
;
206 case CFG_TOKEN_COMPONENT
:
207 sSearch
= "component-id="_ostr
;
209 case CFG_TOKEN_TEMPLATE
:
210 sSearch
= "template-id="_ostr
;
212 case CFG_TOKEN_CONFIGNAME
:
213 sSearch
= "cfg:name="_ostr
;
215 case CFG_TOKEN_OORNAME
:
216 sSearch
= "oor:name="_ostr
;
219 case CFG_TOKEN_OORVALUE
:
220 sSearch
= "oor:value="_ostr
;
222 case CFG_TEXT_START
: {
223 if ( sCurrentResTyp
!= sTokenName
) {
226 sCurrentResTyp
= sTokenName
;
228 OString sTemp
= sToken
.copy( sToken
.indexOf( "xml:lang=" ));
229 sCurrentIsoLang
= sTemp
.getToken(1, '"');
231 if ( sCurrentIsoLang
== NO_TRANSLATE_ISO
)
234 pStackData
->sTextTag
= sToken
;
236 sCurrentText
= ""_ostr
;
241 if ( !sSearch
.isEmpty())
243 OString sTemp
= sToken
.copy( sToken
.indexOf( sSearch
));
244 sTokenId
= sTemp
.getToken(1, '"');
246 pStackData
= aStack
.Push( sTokenName
, sTokenId
);
248 if ( sSearch
== "cfg:name=" ) {
249 OString
sTemp( sToken
.toAsciiUpperCase() );
250 bLocalize
= sTemp
.indexOf("CFG:TYPE=\"STRING\"")>=0
251 && sTemp
.indexOf( "CFG:LOCALIZED=\"TRUE\"" )>=0;
254 else if ( sTokenName
== "label" ) {
255 if ( sCurrentResTyp
!= sTokenName
) {
258 sCurrentResTyp
= sTokenName
;
264 sTokenName
= sToken
.getToken(1, '/').getToken(0, '>').
266 if ( aStack
.GetStackData() && ( aStack
.GetStackData()->GetTagType() == sTokenName
))
268 if (sCurrentText
.isEmpty())
271 pStackData
= aStack
.GetStackData();
275 const OString sError
{ "Misplaced close tag: " + sToken
+ " in file " + global::inputPathname
};
276 yyerror(sError
.getStr());
277 std::exit(EXIT_FAILURE
);
283 sCurrentText
+= sToken
;
287 case CFG_TOKEN_NO_TRANSLATE
:
292 if ( !sCurrentText
.isEmpty() && nToken
!= CFG_TEXTCHAR
)
294 AddText( sCurrentText
, sCurrentIsoLang
, sCurrentResTyp
);
295 Output( sCurrentText
);
296 sCurrentText
.clear();
297 pStackData
->sEndTextTag
= sToken
;
303 if ( sToken
!= " " && sToken
!= "\t" )
304 sLastWhitespace
= ""_ostr
;
307 void CfgExport::Output(const OString
&)
311 void CfgParser::Execute( int nToken
, char * pToken
)
313 OString
sToken( pToken
);
317 if ( sToken
.indexOf( "package-id=" ) != -1 ) {
318 ExecuteAnalyzedToken( CFG_TOKEN_PACKAGE
, pToken
);
320 } else if ( sToken
.indexOf( "component-id=" ) != -1 ) {
321 ExecuteAnalyzedToken( CFG_TOKEN_COMPONENT
, pToken
);
323 } else if ( sToken
.indexOf( "template-id=" ) != -1 ) {
324 ExecuteAnalyzedToken( CFG_TOKEN_TEMPLATE
, pToken
);
326 } else if ( sToken
.indexOf( "cfg:name=" ) != -1 ) {
327 ExecuteAnalyzedToken( CFG_TOKEN_OORNAME
, pToken
);
329 } else if ( sToken
.indexOf( "oor:name=" ) != -1 ) {
330 ExecuteAnalyzedToken( CFG_TOKEN_OORNAME
, pToken
);
332 } else if ( sToken
.indexOf( "oor:value=" ) != -1 ) {
333 ExecuteAnalyzedToken( CFG_TOKEN_OORVALUE
, pToken
);
338 ExecuteAnalyzedToken( nToken
, pToken
);
344 CfgExport::CfgExport(
345 const OString
&rOutputFile
,
347 : sPath(std::move( sFilePath
))
349 pOutputStream
.open( rOutputFile
, PoOfstream::APP
);
350 if (!pOutputStream
.isOpen())
352 std::cerr
<< "ERROR: Unable to open output file: " << rOutputFile
<< "\n";
353 std::exit(EXIT_FAILURE
);
357 CfgExport::~CfgExport()
359 pOutputStream
.close();
363 void CfgExport::WorkOnResourceEnd()
368 if ( pStackData
->sText
["en-US"_ostr
].isEmpty() )
371 OString sXComment
= pStackData
->sText
["x-comment"_ostr
];
372 OString sLocalId
= pStackData
->sIdentifier
;
374 if ( aStack
.size() == 1 ) {
379 sGroupId
= aStack
.GetAccessPath( aStack
.size() - 2 );
383 OString sText
= pStackData
->sText
[ "en-US"_ostr
];
384 sText
= helper::UnQuotHTML( sText
);
386 common::writePoEntry(
387 "Cfgex"_ostr
, pOutputStream
, sPath
, pStackData
->sResTyp
,
388 sGroupId
, sLocalId
, sXComment
, sText
);
391 void CfgExport::WorkOnText(
393 const OString
&rIsoLang
396 if( !rIsoLang
.isEmpty() ) rText
= helper::UnQuotHTML( rText
);
403 const OString
&rMergeSource
, const OString
&rOutputFile
,
404 OString _sFilename
, const OString
&rLanguage
)
405 : sFilename(std::move( _sFilename
)),
409 rOutputFile
.getStr(), std::ios_base::out
| std::ios_base::trunc
);
410 if (!pOutputStream
.is_open())
412 std::cerr
<< "ERROR: Unable to open output file: " << rOutputFile
<< "\n";
413 std::exit(EXIT_FAILURE
);
416 if (!rMergeSource
.isEmpty())
418 pMergeDataFile
.reset(new MergeDataFile(
419 rMergeSource
, global::inputPathname
, true ));
420 if (rLanguage
.equalsIgnoreAsciiCase("ALL") )
422 aLanguages
= pMergeDataFile
->GetLanguages();
424 else aLanguages
.push_back(rLanguage
);
427 aLanguages
.push_back(rLanguage
);
430 CfgMerge::~CfgMerge()
432 pOutputStream
.close();
435 void CfgMerge::WorkOnText(OString
&, const OString
& rLangIndex
)
437 if ( !(pMergeDataFile
&& bLocalize
) )
441 OString sLocalId
= pStackData
->sIdentifier
;
443 if ( aStack
.size() == 1 ) {
448 sGroupId
= aStack
.GetAccessPath( aStack
.size() - 2 );
451 pResData
.reset( new ResData( sGroupId
, sFilename
) );
452 pResData
->sId
= sLocalId
;
453 pResData
->sResTyp
= pStackData
->sResTyp
;
456 if (rLangIndex
.equalsIgnoreAsciiCase("en-US"))
460 void CfgMerge::Output(const OString
& rOutput
)
462 pOutputStream
<< rOutput
;
465 void CfgMerge::WorkOnResourceEnd()
468 if ( pMergeDataFile
&& pResData
&& bLocalize
&& bEnglish
) {
469 MergeEntrys
*pEntrys
= pMergeDataFile
->GetMergeEntrysCaseSensitive( pResData
.get() );
473 for( size_t i
= 0; i
< aLanguages
.size(); ++i
){
474 sCur
= aLanguages
[ i
];
477 pEntrys
->GetText( sContent
, sCur
, true );
479 ( !sCur
.equalsIgnoreAsciiCase("en-US") ) && !sContent
.isEmpty())
481 OString sTextTag
= pStackData
->sTextTag
;
482 const sal_Int32 nLangAttributeStart
{ sTextTag
.indexOf( "xml:lang=" ) };
483 const sal_Int32 nLangStart
{ sTextTag
.indexOf( '"', nLangAttributeStart
)+1 };
484 const sal_Int32 nLangEnd
{ sTextTag
.indexOf( '"', nLangStart
) };
485 OString sAdditionalLine
{ "\t"
486 + sTextTag
.replaceAt(nLangStart
, nLangEnd
-nLangStart
, sCur
)
487 + helper::QuotHTML(sContent
)
488 + pStackData
->sEndTextTag
491 Output( sAdditionalLine
);
500 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */