6daa9cd133
Change-Id: Ice11a7b004ae39b6e455efa8f9cee62225d55147 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139032 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
531 lines
15 KiB
C++
531 lines
15 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 .
|
|
*/
|
|
|
|
#ifdef AIX
|
|
# undef _THREAD_SAFE
|
|
#endif
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include <prewin.h>
|
|
#include <postwin.h>
|
|
#else
|
|
// From MinGW
|
|
typedef unsigned short WORD;
|
|
#define PRIMARYLANGID(lgid) (static_cast<WORD>(lgid) & 0x3ff)
|
|
#define SUBLANGID(lgid) (static_cast<WORD>(lgid) >> 10)
|
|
#define LANG_SPANISH 0x0a
|
|
#define SUBLANG_NEUTRAL 0x00
|
|
#define SUBLANG_SPANISH 0x01
|
|
#endif
|
|
|
|
#include "cmdline.hxx"
|
|
|
|
#include <comphelper/string.hxx>
|
|
#include <osl/thread.h>
|
|
#include <osl/process.h>
|
|
#include <osl/file.hxx>
|
|
#include <sal/main.h>
|
|
|
|
#include <tools/config.hxx>
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <iterator>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#ifndef _WIN32
|
|
#include <cstring>
|
|
#endif
|
|
|
|
namespace /* private */
|
|
{
|
|
|
|
|
|
void ShowUsage()
|
|
{
|
|
std::cout << "Usage: -ulf ulf_file -rc rc_output_file -rct rc_template_file -rch rch_file -rcf rcf_file" << std::endl;
|
|
std::cout << "-ulf Name of the ulf file" << std::endl;
|
|
std::cout << "-rc Name of the resulting resource file" << std::endl;
|
|
std::cout << "-rct Name of the resource template file" << std::endl;
|
|
std::cout << "-rch Name of the resource file header" << std::endl;
|
|
std::cout << "-rcf Name of the resource file footer" << std::endl;
|
|
}
|
|
|
|
OUString OStringToOUString(std::string_view str)
|
|
{ return rtl::OStringToOUString(str, osl_getThreadTextEncoding()); }
|
|
|
|
OString OUStringToOString(std::u16string_view str)
|
|
{ return rtl::OUStringToOString(str, osl_getThreadTextEncoding()); }
|
|
|
|
/** Get the directory where the module
|
|
is located as system directory, the
|
|
returned directory has a trailing '\' */
|
|
OUString get_module_path()
|
|
{
|
|
OUString cwd_url;
|
|
OUString module_path;
|
|
if (osl_Process_E_None == osl_getProcessWorkingDir(&cwd_url.pData))
|
|
osl::FileBase::getSystemPathFromFileURL(cwd_url, module_path);
|
|
|
|
return module_path;
|
|
}
|
|
|
|
/** Make the absolute directory of a base and
|
|
a relative directory, if the relative
|
|
directory is absolute the relative
|
|
directory will be returned unchanged.
|
|
Base and relative directory should be
|
|
system paths the returned directory is
|
|
a system path too */
|
|
OUString get_absolute_path(
|
|
const OUString& BaseDir, const OUString& RelDir)
|
|
{
|
|
OUString base_url;
|
|
OUString rel_url;
|
|
|
|
osl::FileBase::getFileURLFromSystemPath(BaseDir, base_url);
|
|
osl::FileBase::getFileURLFromSystemPath(RelDir, rel_url);
|
|
|
|
OUString abs_url;
|
|
(void)osl::FileBase::getAbsoluteFileURL(base_url, rel_url, abs_url);
|
|
|
|
OUString abs_sys_path;
|
|
osl::FileBase::getSystemPathFromFileURL(abs_url, abs_sys_path);
|
|
|
|
return abs_sys_path;
|
|
}
|
|
|
|
std::string make_absolute(const std::string& file_name)
|
|
{
|
|
OUString fp = get_absolute_path(
|
|
get_module_path(), OStringToOUString(file_name.c_str()));
|
|
return std::string(OUStringToOString(fp));
|
|
}
|
|
|
|
/** A helper class, enables stream exceptions
|
|
on construction, restores the old exception
|
|
state on destruction */
|
|
class StreamExceptionsEnabler
|
|
{
|
|
public:
|
|
explicit StreamExceptionsEnabler(
|
|
std::ios& iostrm ) :
|
|
m_IoStrm(iostrm),
|
|
m_OldIos(m_IoStrm.exceptions())
|
|
{
|
|
m_IoStrm.exceptions(std::ios::failbit | std::ios::badbit);
|
|
}
|
|
|
|
~StreamExceptionsEnabler()
|
|
{
|
|
m_IoStrm.exceptions(m_OldIos);
|
|
}
|
|
private:
|
|
std::ios& m_IoStrm;
|
|
std::ios::iostate m_OldIos;
|
|
};
|
|
|
|
class iso_lang_identifier
|
|
{
|
|
public:
|
|
iso_lang_identifier() {};
|
|
|
|
explicit iso_lang_identifier(const OString& str) :
|
|
maBcp47(str)
|
|
{ }
|
|
|
|
explicit iso_lang_identifier(const std::string& str) :
|
|
maBcp47(str.c_str())
|
|
{ }
|
|
|
|
OUString make_OUString() const
|
|
{ return OStringToOUString( maBcp47, RTL_TEXTENCODING_ASCII_US); }
|
|
|
|
std::string make_std_string() const
|
|
{ return maBcp47.getStr(); }
|
|
|
|
private:
|
|
OString maBcp47;
|
|
};
|
|
|
|
/** Convert an OUString to the MS resource
|
|
file format string e.g.
|
|
OUString -> L"\x1A00\x2200\x3400" */
|
|
std::string make_winrc_unicode_string(const OUString& str)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "L\"";
|
|
|
|
size_t length = str.getLength();
|
|
const sal_Unicode* pchr = str.getStr();
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
oss << "\\x" << std::hex << static_cast<int>(*pchr++);
|
|
|
|
oss << "\"";
|
|
return oss.str();
|
|
}
|
|
|
|
std::string make_winrc_unicode_string(const std::string& str)
|
|
{
|
|
return make_winrc_unicode_string(
|
|
OUString::createFromAscii(str.c_str()));
|
|
}
|
|
|
|
/** A replacement table contains pairs of
|
|
placeholders and the appropriate substitute */
|
|
class Substitutor
|
|
{
|
|
private:
|
|
typedef std::map<std::string, std::string> replacement_table_t;
|
|
typedef std::map<std::string, replacement_table_t> iso_lang_replacement_table_t;
|
|
|
|
public:
|
|
typedef iso_lang_replacement_table_t::iterator iterator;
|
|
typedef iso_lang_replacement_table_t::const_iterator const_iterator;
|
|
|
|
iterator begin()
|
|
{ return iso_lang_replacement_table_.begin(); }
|
|
|
|
iterator end()
|
|
{ return iso_lang_replacement_table_.end(); }
|
|
|
|
public:
|
|
|
|
Substitutor() {};
|
|
|
|
void set_language(const iso_lang_identifier& iso_lang)
|
|
{
|
|
active_iso_lang_ = iso_lang;
|
|
}
|
|
|
|
// If Text is a placeholder substitute it with
|
|
//its substitute else leave it unchanged
|
|
void substitute(std::string& Text)
|
|
{
|
|
replacement_table_t& prt = get_replacement_table(active_iso_lang_.make_std_string());
|
|
replacement_table_t::iterator iter = prt.find(Text);
|
|
if (iter != prt.end())
|
|
Text = iter->second;
|
|
}
|
|
|
|
void add_substitution(
|
|
const std::string& Placeholder, const std::string& Substitute)
|
|
{
|
|
replacement_table_t& prt = get_replacement_table(active_iso_lang_.make_std_string());
|
|
prt.insert(std::make_pair(Placeholder, Substitute));
|
|
}
|
|
|
|
|
|
private:
|
|
// Return the replacement table for the iso lang id
|
|
// create a new one if not already present
|
|
replacement_table_t& get_replacement_table(const std::string& iso_lang)
|
|
{
|
|
return iso_lang_replacement_table_[iso_lang];
|
|
}
|
|
|
|
private:
|
|
iso_lang_replacement_table_t iso_lang_replacement_table_;
|
|
iso_lang_identifier active_iso_lang_;
|
|
};
|
|
|
|
void add_group_entries(
|
|
Config& aConfig,
|
|
const OString& GroupName,
|
|
Substitutor& Substitutor)
|
|
{
|
|
OSL_ASSERT(aConfig.HasGroup(GroupName));
|
|
|
|
aConfig.SetGroup(GroupName);
|
|
size_t key_count = aConfig.GetKeyCount();
|
|
std::map< LanguageType, std::string > map;
|
|
|
|
for (size_t i = 0; i < key_count; i++)
|
|
{
|
|
OString iso_lang = aConfig.GetKeyName(sal::static_int_cast<sal_uInt16>(i));
|
|
OString key_value_utf8 = aConfig.ReadKey(sal::static_int_cast<sal_uInt16>(i));
|
|
iso_lang_identifier myiso_lang( iso_lang );
|
|
LanguageType ltype = LanguageTag( myiso_lang.make_OUString()).makeFallback().getLanguageType();
|
|
if( ( static_cast<sal_uInt16>(ltype) & 0x0200 ) == 0 && map[ ltype ].empty() )
|
|
{
|
|
Substitutor.set_language(iso_lang_identifier(iso_lang));
|
|
|
|
key_value_utf8 = comphelper::string::strip(key_value_utf8, '\"');
|
|
|
|
OUString key_value_utf16 =
|
|
OStringToOUString(key_value_utf8, RTL_TEXTENCODING_UTF8);
|
|
|
|
Substitutor.add_substitution(
|
|
GroupName.getStr(), make_winrc_unicode_string(key_value_utf16));
|
|
map[ ltype ] = std::string( iso_lang.getStr() );
|
|
}
|
|
else
|
|
{
|
|
if( !map[ ltype ].empty() )
|
|
{
|
|
printf("ERROR: Duplicated ms id %d found for the languages %s and %s !!!! This does not work in microsoft resources\nPlease remove one!\n", static_cast<sal_uInt16>(ltype) , map[ ltype ].c_str() , iso_lang.getStr());
|
|
exit( -1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void read_ulf_file(const std::string& FileName, Substitutor& Substitutor)
|
|
{
|
|
// work-around for #i32420#
|
|
|
|
// as the Config class is currently not able to deal correctly with
|
|
// UTF8 files starting with a byte-order-mark we create a copy of the
|
|
// original file without the byte-order-mark
|
|
OUString tmpfile_url;
|
|
osl_createTempFile(nullptr, nullptr, &tmpfile_url.pData);
|
|
|
|
OUString tmpfile_sys;
|
|
osl::FileBase::getSystemPathFromFileURL(tmpfile_url, tmpfile_sys);
|
|
|
|
std::ifstream in(FileName.c_str());
|
|
std::ofstream out(OUStringToOString(tmpfile_sys).getStr());
|
|
|
|
try
|
|
{
|
|
StreamExceptionsEnabler sexc_out(out);
|
|
StreamExceptionsEnabler sexc_in(in);
|
|
|
|
//skip the byte-order-mark 0xEF 0xBB 0xBF, identifying UTF8 files
|
|
unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF};
|
|
char buff[3];
|
|
in.read(&buff[0], 3);
|
|
|
|
if (memcmp(buff, BOM, 3) != 0)
|
|
in.seekg(0);
|
|
|
|
std::string line;
|
|
while (std::getline(in, line))
|
|
out << line << std::endl;
|
|
}
|
|
catch (const std::ios::failure&)
|
|
{
|
|
if (!in.eof())
|
|
throw;
|
|
}
|
|
|
|
|
|
// end work-around for #i32420#
|
|
|
|
Config config(tmpfile_url);
|
|
size_t grpcnt = config.GetGroupCount();
|
|
for (size_t i = 0; i < grpcnt; i++)
|
|
add_group_entries(config, config.GetGroupName(sal::static_int_cast<sal_uInt16>(i)), Substitutor);
|
|
}
|
|
|
|
void read_file(
|
|
const std::string& fname,
|
|
std::vector<std::string>& string_container)
|
|
{
|
|
std::ifstream file(fname.c_str());
|
|
StreamExceptionsEnabler sexc(file);
|
|
|
|
try
|
|
{
|
|
std::string line;
|
|
while (std::getline(file, line))
|
|
string_container.push_back(line);
|
|
}
|
|
catch(const std::ios::failure&)
|
|
{
|
|
if (!file.eof())
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/** A simple helper function that happens the
|
|
content of one file to another one */
|
|
void concatenate_files(std::ostream& os, std::istream& is)
|
|
{
|
|
StreamExceptionsEnabler os_sexc(os);
|
|
StreamExceptionsEnabler is_sexc(is);
|
|
|
|
try
|
|
{
|
|
std::string line;
|
|
while (std::getline(is, line))
|
|
os << line << std::endl;
|
|
}
|
|
catch(const std::ios::failure&)
|
|
{
|
|
if (!is.eof())
|
|
throw;
|
|
}
|
|
}
|
|
|
|
bool is_placeholder(const std::string& str)
|
|
{
|
|
return ((str.length() > 1) &&
|
|
('%' == str[0]) &&
|
|
('%' == str[str.length() - 1]));
|
|
}
|
|
|
|
void start_language_section(
|
|
std::ostream_iterator<std::string>& ostream_iter, const iso_lang_identifier& iso_lang)
|
|
{
|
|
ostream_iter = std::string();
|
|
|
|
std::string lang_section("LANGUAGE ");
|
|
|
|
LanguageType ltype = LanguageTag( iso_lang.make_OUString()).makeFallback().getLanguageType();
|
|
|
|
char buff[10];
|
|
int primLangID = PRIMARYLANGID(ltype);
|
|
int subLangID = SUBLANGID(ltype);
|
|
// Our resources are normally not sub language dependent.
|
|
// Esp. for spanish we don't want to distinguish between trad.
|
|
// and international sorting (which leads to two different sub languages)
|
|
// Setting the sub language to neutral allows us to use one
|
|
// stringlist for all spanish variants
|
|
if ( ( primLangID == LANG_SPANISH ) &&
|
|
( subLangID == SUBLANG_SPANISH ) )
|
|
subLangID = SUBLANG_NEUTRAL;
|
|
|
|
#ifdef _WIN32
|
|
_itoa(primLangID, buff, 16);
|
|
#else
|
|
sprintf(buff, "%x", primLangID);
|
|
#endif
|
|
lang_section += std::string("0x") + std::string(buff);
|
|
|
|
lang_section += std::string(" , ");
|
|
|
|
#ifdef _WIN32
|
|
_itoa(subLangID, buff, 16);
|
|
#else
|
|
sprintf(buff, "%x", subLangID);
|
|
#endif
|
|
lang_section += std::string("0x") + std::string(buff);
|
|
ostream_iter = lang_section;
|
|
}
|
|
|
|
/** Iterate all languages in the substitutor,
|
|
replace the all placeholder and append the
|
|
result to the output file */
|
|
void inflate_rc_template_to_file(
|
|
std::ostream& os, const std::vector<std::string>& rctmpl, Substitutor& substitutor)
|
|
{
|
|
StreamExceptionsEnabler sexc(os);
|
|
|
|
Substitutor::const_iterator iter = substitutor.begin();
|
|
Substitutor::const_iterator iter_end = substitutor.end();
|
|
|
|
std::ostream_iterator<std::string> oi(os, "\n");
|
|
|
|
for ( /**/ ;iter != iter_end; ++iter)
|
|
{
|
|
substitutor.set_language(iso_lang_identifier(iter->first));
|
|
|
|
if (!rctmpl.empty())
|
|
start_language_section(oi, iso_lang_identifier(iter->first));
|
|
|
|
for ( auto& rct : rctmpl)
|
|
{
|
|
std::istringstream iss(rct);
|
|
std::string line;
|
|
|
|
while (iss)
|
|
{
|
|
std::string token;
|
|
iss >> token;
|
|
substitutor.substitute(token);
|
|
|
|
// HACK for partially merged
|
|
// *.lng files where some strings have
|
|
// a particular language that others
|
|
// don't have in order to keep the
|
|
// build
|
|
// coverity[tainted_data] - trusted data source
|
|
if (is_placeholder(token))
|
|
token = make_winrc_unicode_string(token);
|
|
|
|
line += token;
|
|
line += " ";
|
|
}
|
|
oi = line;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace /* private */
|
|
|
|
/* MAIN
|
|
The file names provided via command line should be
|
|
absolute or relative to the directory of this module.
|
|
|
|
Algo:
|
|
1. read the ulf file and initialize the substitutor
|
|
2. read the resource template file
|
|
3. create the output file and append the header
|
|
4. inflate the resource template to the output file
|
|
for every language using the substitutor
|
|
5. append the footer
|
|
*/
|
|
|
|
SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
|
|
{
|
|
try
|
|
{
|
|
CommandLine cmdline(argc, argv);
|
|
|
|
Substitutor substitutor;
|
|
read_ulf_file(make_absolute(cmdline.get_arg("-ulf")), substitutor);
|
|
|
|
std::vector<std::string> rc_tmpl;
|
|
read_file(make_absolute(cmdline.get_arg("-rct")), rc_tmpl);
|
|
|
|
std::ofstream rc_file(make_absolute(cmdline.get_arg("-rc")));
|
|
std::ifstream in_header(make_absolute(cmdline.get_arg("-rch")));
|
|
concatenate_files(rc_file, in_header);
|
|
|
|
inflate_rc_template_to_file(rc_file, rc_tmpl, substitutor);
|
|
|
|
std::ifstream in_footer(make_absolute(cmdline.get_arg("-rcf")));
|
|
concatenate_files(rc_file, in_footer);
|
|
}
|
|
catch(const std::ios::failure& ex)
|
|
{
|
|
std::cout << ex.what() << std::endl;
|
|
return 1;
|
|
}
|
|
catch(const std::exception& ex)
|
|
{
|
|
std::cout << ex.what() << std::endl;
|
|
ShowUsage();
|
|
return 1;
|
|
}
|
|
catch(...)
|
|
{
|
|
std::cout << "Unexpected error..." << std::endl;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|