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