Fix typos
[LibreOffice.git] / l10ntools / source / localize.cxx
blob38e9c21f3cac5d00e7665d506cbfa022ec4e3f03
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <cassert>
23 #include <cstddef>
24 #include <cstdlib>
25 #include <iostream>
26 #include <string>
27 #include <string_view>
28 #include <map>
29 #include <vector>
30 #include <algorithm>
32 #include <osl/file.h>
33 #include <osl/file.hxx>
34 #include <osl/thread.h>
35 #include <rtl/string.h>
36 #include <rtl/string.hxx>
37 #include <rtl/textcvt.h>
38 #include <rtl/strbuf.hxx>
39 #include <rtl/ustring.h>
40 #include <rtl/ustring.hxx>
41 #include <sal/macros.h>
42 #include <sal/main.h>
43 #include <sal/types.h>
45 #include <po.hxx>
47 using namespace std;
49 namespace {
51 bool matchList(
52 const OUString& rUrl, const std::u16string_view* pList, size_t nLength)
54 for (size_t i = 0; i != nLength; ++i) {
55 if (rUrl.endsWith(pList[i])) {
56 return true;
59 return false;
62 bool passesNegativeList(const OUString& rUrl) {
63 static const std::u16string_view list[] = {
64 u"/desktop/test/deployment/passive/help/en/help.tree",
65 u"/desktop/test/deployment/passive/help/en/main.xhp",
66 u"/dictionaries.xcu",
67 u"/dictionaries/da_DK/help/da/help.tree",
68 (u"/dictionaries/da_DK/help/da/"
69 "org.openoffice.da.hunspell.dictionaries/page1.xhp"),
70 (u"/dictionaries/da_DK/help/da/"
71 "org.openoffice.da.hunspell.dictionaries/page2.xhp"),
72 u"/dictionaries/hu_HU/help/hu/help.tree",
73 (u"/dictionaries/hu_HU/help/hu/"
74 "org.openoffice.hu.hunspell.dictionaries/page1.xhp"),
75 u"/officecfg/registry/data/org/openoffice/Office/Accelerators.xcu"
77 return !matchList(rUrl, list, SAL_N_ELEMENTS(list));
80 bool passesPositiveList(const OUString& rUrl) {
81 static const std::u16string_view list[] = {
82 u"/description.xml"
84 return matchList(rUrl, list, SAL_N_ELEMENTS(list));
87 void handleCommand(
88 std::string_view rInPath, std::string_view rOutPath,
89 const std::string& rExecutable)
91 OStringBuffer buf;
92 if (rExecutable == "uiex" || rExecutable == "hrcex")
94 auto const env = getenv("SRC_ROOT");
95 assert(env != nullptr);
96 buf.append(env);
97 buf.append("/solenv/bin/");
99 else
101 auto const env = getenv("WORKDIR_FOR_BUILD");
102 assert(env != nullptr);
103 buf.append(env);
104 buf.append("/LinkTarget/Executable/");
106 buf.append(rExecutable.data());
107 buf.append(" -i ");
108 buf.append(rInPath);
109 buf.append(" -o ");
110 buf.append(rOutPath);
112 const OString cmd = buf.makeStringAndClear();
113 if (system(cmd.getStr()) != 0)
115 cerr << "Error: Failed to execute " << cmd << '\n';
116 throw false; //TODO
120 void InitPoFile(
121 std::string_view rProject, const OString& rInPath,
122 const OString& rPotDir, const OString& rOutPath )
124 //Create directory for po file
126 OUString outDir =
127 OStringToOUString(
128 rPotDir.subView(0,rPotDir.lastIndexOf('/')), RTL_TEXTENCODING_UTF8);
129 OUString outDirUrl;
130 if (osl::FileBase::getFileURLFromSystemPath(outDir, outDirUrl)
131 != osl::FileBase::E_None)
133 cerr
134 << ("Error: Cannot convert pathname to URL in " __FILE__
135 ", in line ")
136 << __LINE__ << "\n outDir: "
137 << outDir
138 << "\n";
139 throw false; //TODO
141 osl::Directory::createPath(outDirUrl);
144 //Add header to the po file
145 PoOfstream aPoOutPut;
146 aPoOutPut.open(rOutPath.getStr());
147 if (!aPoOutPut.isOpen())
149 cerr
150 << "Error: Cannot open po file "
151 << rOutPath << "\n";
152 throw false; //TODO
155 const sal_Int32 nProjectInd = rInPath.indexOf(rProject);
156 const OString relativPath =
157 rInPath.copy(nProjectInd, rInPath.lastIndexOf('/')- nProjectInd);
159 PoHeader aTmp(relativPath);
160 aPoOutPut.writeHeader(aTmp);
161 aPoOutPut.close();
164 bool fileExists(const OString& fileName)
166 FILE *f = fopen(fileName.getStr(), "r");
168 if (f != nullptr)
170 fclose(f);
171 return true;
174 return false;
177 OString gDestRoot;
179 bool handleFile(std::string_view rProject, const OUString& rUrl, const OString& rPotDir)
181 struct Command {
182 std::u16string_view extension;
183 std::string executable;
184 bool positive;
186 static Command const commands[] = {
187 { std::u16string_view(u".hrc"), "hrcex", false },
188 { std::u16string_view(u".ulf"), "ulfex", false },
189 { std::u16string_view(u".xcu"), "cfgex", false },
190 { std::u16string_view(u".xrm"), "xrmex", false },
191 { std::u16string_view(u"description.xml"), "xrmex", true },
192 { std::u16string_view(u".xhp"), "helpex", false },
193 { std::u16string_view(u".properties"), "propex", false },
194 { std::u16string_view(u".ui"), "uiex", false },
195 { std::u16string_view(u".tree"), "treex", false } };
196 for (size_t i = 0; i != SAL_N_ELEMENTS(commands); ++i)
198 if (rUrl.endsWith(commands[i].extension) &&
199 (commands[i].executable != "propex" || rUrl.indexOf("en_US") != -1))
201 if (commands[i].positive ? passesPositiveList(rUrl) : passesNegativeList(rUrl))
203 //Get input file path
204 OString sInPath;
206 OUString sInPathTmp;
207 if (osl::FileBase::getSystemPathFromFileURL(rUrl, sInPathTmp) !=
208 osl::FileBase::E_None)
210 cerr << "osl::FileBase::getSystemPathFromFileURL(" << rUrl << ") failed\n";
211 throw false; //TODO
213 sInPath = OUStringToOString( sInPathTmp, RTL_TEXTENCODING_UTF8 );
215 OString sOutPath;
216 bool bCreatedFile = false;
217 bool bSimpleModuleCase = commands[i].executable == "uiex" || commands[i].executable == "hrcex";
218 if (bSimpleModuleCase)
219 sOutPath = gDestRoot + "/" + rProject + "/messages.pot";
220 else
221 sOutPath = rPotDir + ".pot";
223 if (!fileExists(sOutPath))
225 InitPoFile(rProject, sInPath, rPotDir, sOutPath);
226 bCreatedFile = true;
228 handleCommand(sInPath, sOutPath, commands[i].executable);
231 //Delete pot file if it contain only the header
232 PoIfstream aPOStream(sOutPath);
233 PoEntry aPO;
234 aPOStream.readEntry( aPO );
235 bool bDel = aPOStream.eof();
236 aPOStream.close();
238 if (bDel)
240 if ( system(OString("rm " + sOutPath).getStr()) != 0 )
242 cerr
243 << "Error: Cannot remove entryless pot file: "
244 << sOutPath << "\n";
245 throw false; //TODO
248 else if (bCreatedFile && bSimpleModuleCase)
250 // add one stock Add, Cancel, Close, Help, No, OK, Yes entry to each module.po
251 // and duplicates in .ui files then filtered out by solenv/bin/uiex
253 std::ofstream aOutPut;
254 aOutPut.open(sOutPath.getStr(), std::ios_base::out | std::ios_base::app);
256 aOutPut << "#. wH3TZ\nmsgctxt \"stock\"\nmsgid \"_Add\"\nmsgstr \"\"\n\n";
257 aOutPut << "#. S9dsC\nmsgctxt \"stock\"\nmsgid \"_Apply\"\nmsgstr \"\"\n\n";
258 aOutPut << "#. TMo6G\nmsgctxt \"stock\"\nmsgid \"_Cancel\"\nmsgstr \"\"\n\n";
259 aOutPut << "#. MRCkv\nmsgctxt \"stock\"\nmsgid \"_Close\"\nmsgstr \"\"\n\n";
260 aOutPut << "#. nvx5t\nmsgctxt \"stock\"\nmsgid \"_Delete\"\nmsgstr \"\"\n\n";
261 aOutPut << "#. YspCj\nmsgctxt \"stock\"\nmsgid \"_Edit\"\nmsgstr \"\"\n\n";
262 aOutPut << "#. imQxr\nmsgctxt \"stock\"\nmsgid \"_Help\"\nmsgstr \"\"\n\n";
263 aOutPut << "#. RbjyB\nmsgctxt \"stock\"\nmsgid \"_New\"\nmsgstr \"\"\n\n";
264 aOutPut << "#. dx2yy\nmsgctxt \"stock\"\nmsgid \"_No\"\nmsgstr \"\"\n\n";
265 aOutPut << "#. M9DsL\nmsgctxt \"stock\"\nmsgid \"_OK\"\nmsgstr \"\"\n\n";
266 aOutPut << "#. VtJS9\nmsgctxt \"stock\"\nmsgid \"_Remove\"\nmsgstr \"\"\n\n";
267 aOutPut << "#. C69Fy\nmsgctxt \"stock\"\nmsgid \"_Reset\"\nmsgstr \"\"\n\n";
268 aOutPut << "#. mgpxh\nmsgctxt \"stock\"\nmsgid \"_Yes\"\nmsgstr \"\"\n";
270 aOutPut.close();
275 return true;
277 break;
280 return false;
283 void handleFilesOfDir(
284 std::vector<OUString>& aFiles, std::string_view rProject,
285 const OString& rPotDir )
287 ///Handle files in lexical order
288 std::sort(aFiles.begin(), aFiles.end());
290 for (auto const& elem : aFiles)
291 handleFile(rProject, elem, rPotDir);
294 bool includeProject(std::string_view rProject) {
295 static const char *projects[] = {
296 "include",
297 "accessibility",
298 "avmedia",
299 "basctl",
300 "basic",
301 "chart2",
302 "connectivity",
303 "cui",
304 "dbaccess",
305 "desktop",
306 "dictionaries",
307 "editeng",
308 "extensions",
309 "extras",
310 "filter",
311 "forms",
312 "formula",
313 "fpicker",
314 "framework",
315 "helpcontent2",
316 "instsetoo_native",
317 "librelogo",
318 "mysqlc",
319 "nlpsolver",
320 "officecfg",
321 "oox",
322 "readlicense_oo",
323 "reportbuilder",
324 "reportdesign",
325 "sc",
326 "scaddins",
327 "sccomp",
328 "scp2",
329 "sd",
330 "sdext",
331 "setup_native",
332 "sfx2",
333 "shell",
334 "starmath",
335 "svl",
336 "svtools",
337 "svx",
338 "sw",
339 "swext",
340 "sysui",
341 "uui",
342 "vcl",
343 "wizards",
344 "writerperfect",
345 "xmlsecurity" };
346 for (size_t i = 0; i != SAL_N_ELEMENTS(projects); ++i) {
347 if (rProject == projects[i]) {
348 return true;
351 return false;
354 /// Handle one directory in the hierarchy.
356 /// Ignores symlinks and instead explicitly descends into clone/* or src/*,
357 /// as the Cygwin symlinks are not supported by osl::Directory on Windows.
359 /// @param rUrl the absolute file URL of this directory
361 /// @param nLevel 0 if this is the root directory (core repository)
362 /// that contains the individual modules. 1 if it is a toplevel module and
363 /// larger values for the subdirectories.
365 /// @param rProject the name of the project (empty and ignored if nLevel <= 0)
366 /// @param rPotDir the path of pot directory
367 void handleDirectory(
368 const OUString& rUrl, int nLevel,
369 const OString& rProject, const OString& rPotDir)
371 osl::Directory dir(rUrl);
372 if (dir.open() != osl::FileBase::E_None) {
373 cerr
374 << "Error: Cannot open directory: " << rUrl << '\n';
375 throw false; //TODO
377 std::vector<OUString> aFileNames;
378 std::map<OUString, std::map<OString, OString>> aSubDirs;
379 for (;;) {
380 osl::DirectoryItem item;
381 osl::FileBase::RC e = dir.getNextItem(item);
382 if (e == osl::FileBase::E_NOENT) {
383 break;
385 if (e != osl::FileBase::E_None) {
386 cerr << "Error: Cannot read directory\n";
387 throw false; //TODO
389 osl::FileStatus stat(
390 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName
391 | osl_FileStatus_Mask_FileURL);
392 if (item.getFileStatus(stat) != osl::FileBase::E_None) {
393 cerr << "Error: Cannot get file status\n";
394 throw false; //TODO
396 const OString sDirName =
397 OUStringToOString(stat.getFileName(),RTL_TEXTENCODING_UTF8);
398 switch (nLevel)
400 case 0: // a root directory
401 if (stat.getFileType() == osl::FileStatus::Directory && includeProject(sDirName))
402 aSubDirs[stat.getFileURL()][sDirName] = rPotDir + "/" + sDirName;
403 break;
404 default:
405 if (stat.getFileType() == osl::FileStatus::Directory)
406 aSubDirs[stat.getFileURL()][rProject] = rPotDir + "/" + sDirName;
407 else
408 aFileNames.push_back(stat.getFileURL());
409 break;
413 OString aPotDir(rPotDir);
414 if( !aFileNames.empty() )
416 OString aProject(rProject);
417 if (aProject == "include" && nLevel > 1)
419 aProject = aPotDir.copy(aPotDir.lastIndexOf('/') + 1);
420 aPotDir = aPotDir.subView(0, aPotDir.lastIndexOf("include")) + aProject + "/messages";
422 if (aProject != "include")
424 handleFilesOfDir(aFileNames, aProject, aPotDir);
428 if (dir.close() != osl::FileBase::E_None) {
429 cerr << "Error: Cannot close directory\n";
430 throw false; //TODO
433 for (auto const& elem : aSubDirs)
434 handleDirectory(elem.first, nLevel + 1, elem.second.begin()->first,
435 elem.second.begin()->second);
437 //Remove empty pot directory
438 OUString sPoPath =
439 OStringToOUString(
440 aPotDir.subView(0,aPotDir.lastIndexOf('/')), RTL_TEXTENCODING_UTF8);
441 OUString sPoUrl;
442 if (osl::FileBase::getFileURLFromSystemPath(sPoPath, sPoUrl)
443 != osl::FileBase::E_None)
445 cerr
446 << ("Error: Cannot convert pathname to URL in " __FILE__
447 ", in line ")
448 << __LINE__ << "\n"
449 << sPoPath
450 << "\n";
451 throw false; //TODO
453 osl::Directory::remove(sPoUrl);
456 void handleProjects(char const * sSourceRoot, char const * sDestRoot)
458 OUString root16;
459 if (!rtl_convertStringToUString(
460 &root16.pData, sSourceRoot, rtl_str_getLength(sSourceRoot),
461 osl_getThreadTextEncoding(),
462 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
463 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
464 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
466 cerr << "Error: Cannot convert pathname to UTF-16\n";
467 throw false; //TODO
469 OUString rootUrl;
470 if (osl::FileBase::getFileURLFromSystemPath(root16, rootUrl)
471 != osl::FileBase::E_None)
473 cerr
474 << ("Error: Cannot convert pathname to URL in " __FILE__
475 ", in line ")
476 << __LINE__ << "\n root16: "
477 << root16
478 << "\n";
479 throw false; //TODO
481 gDestRoot = OString(sDestRoot);
482 handleDirectory(rootUrl, 0, OString(), gDestRoot);
486 SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
490 if (argc != 3)
492 cerr
493 << ("localize (c)2001 by Sun Microsystems\n\n"
494 "As part of the L10N framework, localize extracts en-US\n"
495 "strings for translation out of the toplevel modules defined\n"
496 "in projects array in l10ntools/source/localize.cxx.\n\n"
497 "Syntax: localize <source-root> <outfile>\n");
498 exit(EXIT_FAILURE);
500 handleProjects(argv[1],argv[2]);
502 catch (std::exception& e)
504 cerr << "exception: " << e.what() << std::endl;
505 return EXIT_FAILURE;
507 catch (bool) //TODO
509 return EXIT_FAILURE;
511 return EXIT_SUCCESS;
514 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */