1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_shell.hxx"
26 
27 #include <tools/presys.h>
28 #if defined _MSC_VER
29 #pragma warning(push, 1)
30 #endif
31 #include <windows.h>
32 #if defined _MSC_VER
33 #pragma warning(pop)
34 #endif
35 #include <tools/postsys.h>
36 
37 #define VCL_NEED_BASETSD
38 
39 #include "cmdline.hxx"
40 
41 #include "osl/thread.h"
42 #include "osl/process.h"
43 #include "osl/file.hxx"
44 #include "sal/main.h"
45 
46 #include "tools/config.hxx"
47 #include "i18npool/mslangid.hxx"
48 
49 #include <iostream>
50 #include <fstream>
51 #include <map>
52 #include <sstream>
53 #include <iterator>
54 #include <algorithm>
55 #include <string>
56 
57 namespace /* private */
58 {
59 
60 using rtl::OUString;
61 using rtl::OString;
62 
63 //###########################################
64 void ShowUsage()
65 {
66 	std::cout << "Usage: -ulf ulf_file -rc rc_output_file -rct rc_template_file -rch rch_file -rcf rcf_file" << std::endl;
67 	std::cout << "-ulf Name of the ulf file" << std::endl;
68 	std::cout << "-rc  Name of the resulting resource file" << std::endl;
69 	std::cout << "-rct Name of the resource template file" << std::endl;
70 	std::cout << "-rch Name of the resource file header" << std::endl;
71 	std::cout << "-rcf Name of the resource file footer" << std::endl;
72 }
73 
74 //###########################################
75 inline OUString OStringToOUString(const OString& str)
76 { return rtl::OStringToOUString(str, osl_getThreadTextEncoding()); }
77 
78 //###########################################
79 inline OString OUStringToOString(const OUString& str)
80 { return rtl::OUStringToOString(str, osl_getThreadTextEncoding()); }
81 
82 //###########################################
83 /** Get the directory where the module
84     is located as system directory, the
85     returned directory has a trailing '\'  */
86 OUString get_module_path()
87 {
88     OUString cwd_url;
89     OUString module_path;
90     if (osl_Process_E_None == osl_getProcessWorkingDir(&cwd_url.pData))
91         osl::FileBase::getSystemPathFromFileURL(cwd_url, module_path);
92 
93     return module_path;
94 }
95 
96 //###########################################
97 /** Make the absolute directory of a base and
98 	a relative directory, if the relative
99 	directory is absolute the relative
100 	directory will be returned unchanged.
101 	Base and relative directory should be
102 	system paths the returned directory is
103 	a system path too */
104 OUString get_absolute_path(
105 	const OUString& BaseDir, const OUString& RelDir)
106 {
107     OUString base_url;
108     OUString rel_url;
109 
110     osl::FileBase::getFileURLFromSystemPath(BaseDir, base_url);
111     osl::FileBase::getFileURLFromSystemPath(RelDir, rel_url);
112 
113     OUString abs_url;
114     osl::FileBase::getAbsoluteFileURL(base_url, rel_url, abs_url);
115 
116     OUString abs_sys_path;
117     osl::FileBase::getSystemPathFromFileURL(abs_url, abs_sys_path);
118 
119     return abs_sys_path;
120 }
121 
122 //###########################################
123 OString get_absolute_file_path(const std::string& file_name)
124 {
125     OUString fp = get_absolute_path(
126         get_module_path(), OStringToOUString(file_name.c_str()));
127     return OUStringToOString(fp);
128 }
129 
130 //###########################################
131 /** A helper class, enables stream exceptions
132 	on construction, restors the old exception
133 	state on destruction */
134 class StreamExceptionsEnabler
135 {
136 public:
137 	explicit StreamExceptionsEnabler(
138 		std::ios& iostrm,
139 		std::ios::iostate NewIos = std::ios::failbit | std::ios::badbit) :
140 		m_IoStrm(iostrm),
141 		m_OldIos(m_IoStrm.exceptions())
142 	{
143 		m_IoStrm.exceptions(NewIos);
144 	}
145 
146 	~StreamExceptionsEnabler()
147 	{
148 		m_IoStrm.exceptions(m_OldIos);
149 	}
150 private:
151 	std::ios& m_IoStrm;
152 	std::ios::iostate m_OldIos;
153 };
154 
155 typedef std::vector<std::string> string_container_t;
156 
157 //###########################################
158 class iso_lang_identifier
159 {
160 public:
161     iso_lang_identifier() {};
162 
163     iso_lang_identifier(const OString& str) :
164         lang_(str)
165     { init(); }
166 
167     iso_lang_identifier(const std::string& str) :
168         lang_(str.c_str())
169     { init(); }
170 
171     OString language() const
172     { return lang_; }
173 
174     OString country() const
175     { return country_; }
176 
177     OString make_OString() const
178     { return lang_ + "-" + country_; }
179 
180     std::string make_std_string() const
181     {
182         OString tmp(lang_ + "-" + country_);
183         return tmp.getStr();
184     }
185 
186 private:
187     void init()
188     {
189         sal_Int32 idx = lang_.indexOf("-");
190 
191         if (idx > -1)
192         {
193             country_ = lang_.copy(idx + 1);
194             lang_ = lang_.copy(0, idx);
195         }
196     }
197 
198 private:
199     OString lang_;
200     OString country_;
201 };
202 
203 //###########################################
204 /** Convert a OUString to the MS resource
205     file format string e.g.
206 	OUString -> L"\x1A00\x2200\x3400" */
207 std::string make_winrc_unicode_string(const OUString& str)
208 {
209 	std::ostringstream oss;
210 	oss << "L\"";
211 
212 	size_t length = str.getLength();
213 	const sal_Unicode* pchr = str.getStr();
214 
215 	for (size_t i = 0; i < length; i++)
216 		oss << "\\x" << std::hex << (int)*pchr++;
217 
218 	oss << "\"";
219 	return oss.str();
220 }
221 
222 //###########################################
223 std::string make_winrc_unicode_string(const std::string& str)
224 {
225     return make_winrc_unicode_string(
226         OUString::createFromAscii(str.c_str()));
227 }
228 
229 //################################################
230 /** A replacement table contains pairs of
231     placeholders and the appropriate substitute */
232 class Substitutor
233 {
234 private:
235 	typedef std::map<std::string, std::string> replacement_table_t;
236 	typedef std::map<std::string, replacement_table_t*> iso_lang_replacement_table_t;
237 
238 public:
239 	typedef iso_lang_replacement_table_t::iterator iterator;
240 	typedef iso_lang_replacement_table_t::const_iterator const_iterator;
241 
242     iterator begin()
243     { return iso_lang_replacement_table_.begin(); }
244 
245     iterator end()
246     { return iso_lang_replacement_table_.end(); }
247 
248 public:
249 
250 	Substitutor() {};
251 
252 	~Substitutor()
253 	{
254 		iso_lang_replacement_table_t::iterator iter_end = iso_lang_replacement_table_.end();
255 		iso_lang_replacement_table_t::iterator iter = iso_lang_replacement_table_.begin();
256 
257 		for( /* no init */; iter != iter_end; ++iter)
258 			delete iter->second;
259 
260 		iso_lang_replacement_table_.clear();
261 	}
262 
263 	void set_language(const iso_lang_identifier& iso_lang)
264 	{
265 		active_iso_lang_ = iso_lang;
266 	}
267 
268 	// If Text is a placeholder substitute it with
269 	//its substitute else leave it unchanged
270 	void substitute(std::string& Text)
271 	{
272 		replacement_table_t* prt = get_replacement_table(active_iso_lang_.make_std_string());
273 		OSL_ASSERT(prt);
274 		replacement_table_t::iterator iter = prt->find(Text);
275 		if (iter != prt->end())
276 			Text = iter->second;
277 	}
278 
279 	void add_substitution(
280 		const std::string& Placeholder, const std::string& Substitute)
281 	{
282 		replacement_table_t* prt = get_replacement_table(active_iso_lang_.make_std_string());
283 		OSL_ASSERT(prt);
284 		prt->insert(std::make_pair(Placeholder, Substitute));
285 	}
286 
287 
288 private:
289 	// Return the replacement table for the iso lang id
290 	// create a new one if not already present
291 	replacement_table_t* get_replacement_table(const std::string& iso_lang)
292 	{
293 		iso_lang_replacement_table_t::iterator iter =
294 		    iso_lang_replacement_table_.find(iso_lang);
295 
296 		replacement_table_t* prt = NULL;
297 
298 		if (iso_lang_replacement_table_.end() == iter)
299 		{
300 			prt = new replacement_table_t();
301 			iso_lang_replacement_table_.insert(std::make_pair(iso_lang, prt));
302 		}
303 		else
304 		{
305 			prt = iter->second;
306 		}
307 		return prt;
308 	}
309 
310 private:
311 	iso_lang_replacement_table_t iso_lang_replacement_table_;
312 	iso_lang_identifier active_iso_lang_;
313 };
314 
315 typedef std::map< unsigned short , std::string , std::less< unsigned short > > shortmap;
316 
317 //###########################################
318 void add_group_entries(
319 	Config& aConfig,
320 	const ByteString& GroupName,
321 	Substitutor& Substitutor)
322 {
323 	OSL_ASSERT(aConfig.HasGroup(GroupName));
324 
325 	aConfig.SetGroup(GroupName);
326 	size_t key_count = aConfig.GetKeyCount();
327     shortmap map;
328 
329 	for (size_t i = 0; i < key_count; i++)
330 	{
331 		ByteString iso_lang = aConfig.GetKeyName(sal::static_int_cast<USHORT>(i));
332 		ByteString key_value_utf8 = aConfig.ReadKey(sal::static_int_cast<USHORT>(i));
333         iso_lang_identifier myiso_lang( iso_lang );
334         LanguageType ltype = MsLangId::convertIsoNamesToLanguage(myiso_lang.language(), myiso_lang.country());
335         if(  ( ltype & 0x0200 ) == 0 && map[ ltype ].empty()  )
336         {
337             Substitutor.set_language(iso_lang_identifier(iso_lang));
338 
339 		    key_value_utf8.EraseLeadingAndTrailingChars('\"');
340 
341 		    OUString key_value_utf16 =
342 			    rtl::OStringToOUString(key_value_utf8, RTL_TEXTENCODING_UTF8);
343 
344 		    Substitutor.add_substitution(
345 			    GroupName.GetBuffer(), make_winrc_unicode_string(key_value_utf16));
346             map[ static_cast<unsigned short>(ltype) ] = std::string( iso_lang.GetBuffer() );
347         }
348         else
349         {
350             if( !map[ ltype ].empty() )
351             {
352                 printf("ERROR: Duplicated ms id %d found for the languages %s and %s !!!! This does not work in microsoft resources\nPlease remove one!\n", ltype , map[ ltype ].c_str() , iso_lang.GetBuffer());
353                 exit( -1 );
354             }
355         }
356 	}
357 }
358 
359 //###########################################
360 void read_ulf_file(const std::string& FileName, Substitutor& Substitutor)
361 {
362     // work-around for #i32420#
363 
364     // as the Config class is currently not able to deal correctly with
365     // UTF8 files starting with a byte-order-mark we create a copy of the
366     // original file without the byte-order-mark
367     rtl::OUString tmpfile_url;
368     osl_createTempFile(NULL, NULL, &tmpfile_url.pData);
369 
370     rtl::OUString tmpfile_sys;
371     osl::FileBase::getSystemPathFromFileURL(tmpfile_url, tmpfile_sys);
372 
373     std::ifstream in(FileName.c_str());
374     std::ofstream out(OUStringToOString(tmpfile_sys).getStr());
375 
376     try
377     {
378         StreamExceptionsEnabler sexc_out(out);
379         StreamExceptionsEnabler sexc_in(in);
380 
381         //skip the byte-order-mark 0xEF 0xBB 0xBF, identifying UTF8 files
382         unsigned char BOM[3] = {0xEF, 0xBB, 0xBF};
383         char buff[3];
384         in.read(&buff[0], 3);
385 
386         if (memcmp(buff, BOM, 3) != 0)
387             in.seekg(0);
388 
389         std::string line;
390         while (std::getline(in, line))
391             out << line << std::endl;
392     }
393     catch (const std::ios::failure&)
394     {
395         if (!in.eof())
396             throw;
397     }
398 
399 	//Config config(OStringToOUString(FileName.c_str()).getStr());
400 
401 	// end work-around for #i32420#
402 
403 	Config config(tmpfile_url.getStr());
404 	size_t grpcnt = config.GetGroupCount();
405 	for (size_t i = 0; i < grpcnt; i++)
406 		add_group_entries(config, config.GetGroupName(sal::static_int_cast<USHORT>(i)), Substitutor);
407 }
408 
409 //###########################################
410 void read_file(
411     const std::string& fname,
412     string_container_t& string_container)
413 {
414     std::ifstream file(fname.c_str());
415 	StreamExceptionsEnabler sexc(file);
416 
417 	try
418 	{
419 		std::string line;
420 		while (std::getline(file, line))
421 			string_container.push_back(line);
422 	}
423 	catch(const std::ios::failure&)
424 	{
425 		if (!file.eof())
426 			throw;
427 	}
428 }
429 
430 //###########################################
431 /** A simple helper function that appens the
432     content of one file to another one  */
433 void concatenate_files(std::ostream& os, std::istream& is)
434 {
435 	StreamExceptionsEnabler os_sexc(os);
436 	StreamExceptionsEnabler is_sexc(is);
437 
438 	try
439 	{
440 		std::string line;
441 		while (std::getline(is, line))
442 		    os << line << std::endl;
443 	}
444 	catch(const std::ios::failure&)
445 	{
446 		if (!is.eof())
447 			throw;
448 	}
449 }
450 
451 //###########################################
452 bool is_placeholder(const std::string& str)
453 {
454     return ((str.length() > 1) &&
455             ('%' == str[0]) &&
456             ('%' == str[str.length() - 1]));
457 }
458 
459 //###########################################
460 void start_language_section(
461     std::ostream_iterator<std::string>& ostream_iter, const iso_lang_identifier& iso_lang)
462 {
463     ostream_iter = std::string();
464 
465     std::string lang_section("LANGUAGE ");
466 
467     LanguageType ltype = MsLangId::convertIsoNamesToLanguage(iso_lang.language(), iso_lang.country());
468 
469     char buff[10];
470     int primLangID = PRIMARYLANGID(ltype);
471     int subLangID = SUBLANGID(ltype);
472     // Our resources are normaly not sub language dependent.
473     // Esp. for spanish we don't want to distinguish between trad.
474     // and internatinal sorting ( which leads to two different sub languages )
475     // Setting the sub language to neutral allows us to use one
476     // stringlist for all spanish variants ( see #123126# )
477     if ( ( primLangID == LANG_SPANISH ) &&
478          ( subLangID == SUBLANG_SPANISH ) )
479         subLangID = SUBLANG_NEUTRAL;
480 
481     _itoa(primLangID, buff, 16);
482     lang_section += std::string("0x") + std::string(buff);
483 
484     lang_section += std::string(" , ");
485 
486     _itoa(subLangID, buff, 16);
487 
488     lang_section += std::string("0x") + std::string(buff);
489     ostream_iter = lang_section;
490 }
491 
492 //###########################################
493 /** Iterate all languages in the substitutor,
494 	replace the all placeholder and append the
495 	result to the output file */
496 void inflate_rc_template_to_file(
497 	std::ostream& os, const string_container_t& rctmpl, Substitutor& substitutor)
498 {
499 	StreamExceptionsEnabler sexc(os);
500 
501 	Substitutor::const_iterator iter = substitutor.begin();
502 	Substitutor::const_iterator iter_end = substitutor.end();
503 
504 	std::ostream_iterator<std::string> oi(os, "\n");
505 
506 	for ( /**/ ;iter != iter_end; ++iter)
507 	{
508 		substitutor.set_language(iso_lang_identifier(iter->first));
509 
510 		string_container_t::const_iterator rct_iter	= rctmpl.begin();
511 		string_container_t::const_iterator rct_iter_end = rctmpl.end();
512 
513         if (!rctmpl.empty())
514             start_language_section(oi, iter->first);
515 
516 		for ( /**/ ;rct_iter != rct_iter_end; ++rct_iter)
517 		{
518 			std::istringstream iss(*rct_iter);
519 			std::string line;
520 
521 			while (iss)
522 			{
523 				std::string token;
524 				iss >> token;
525 				substitutor.substitute(token);
526 
527 				// #110274# HACK for partially merged
528 				// *.lng files where some strings have
529 				// a particular language that others
530 				// don't have in order to keep the
531 				// build
532 				if (is_placeholder(token))
533                     token = make_winrc_unicode_string(token);
534 
535 				line += token;
536 				line += " ";
537 			}
538 			oi = line;
539 		}
540 	}
541 }
542 
543 } // namespace /* private */
544 
545 //####################################################
546 /* MAIN
547    The file names provided via command line should be
548    absolute or relative to the directory of this module.
549 
550    Algo:
551    1. read the ulf file and initialize the substitutor
552    2. read the resource template file
553    3. create the output file and append the header
554    4. inflate the resource template to the output file
555       for every language using the substitutor
556    5. append the footer
557 */
558 #define MAKE_ABSOLUTE(s) (get_absolute_file_path((s)).getStr())
559 #define ULF_FILE(c)    MAKE_ABSOLUTE((c).get_arg("-ulf"))
560 #define RC_TEMPLATE(c) MAKE_ABSOLUTE((c).get_arg("-rct"))
561 #define RC_FILE(c)     MAKE_ABSOLUTE((c).get_arg("-rc"))
562 #define RC_HEADER(c)   MAKE_ABSOLUTE((c).get_arg("-rch"))
563 #define RC_FOOTER(c)   MAKE_ABSOLUTE((c).get_arg("-rcf"))
564 
565 SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
566 {
567 	try
568 	{
569 		CommandLine cmdline(argc, argv);
570 
571 		Substitutor substitutor;
572 		read_ulf_file(ULF_FILE(cmdline), substitutor);
573 
574         string_container_t rc_tmpl;
575 		read_file(RC_TEMPLATE(cmdline), rc_tmpl);
576 
577 		std::ofstream rc_file(RC_FILE(cmdline));
578         std::ifstream in_header(RC_HEADER(cmdline));
579 		concatenate_files(rc_file, in_header);
580 
581 		inflate_rc_template_to_file(rc_file, rc_tmpl, substitutor);
582 
583         std::ifstream in_footer(RC_FOOTER(cmdline));
584 		concatenate_files(rc_file, in_footer);
585 	}
586 	catch(const std::ios::failure& ex)
587 	{
588 		std::cout << ex.what() << std::endl;
589 	}
590 	catch(std::exception& ex)
591 	{
592 		std::cout << ex.what() << std::endl;
593 		ShowUsage();
594 	}
595 	catch(...)
596 	{
597 		std::cout << "Unexpected error..." << std::endl;
598 	}
599 	return 0;
600 }
601 
602