1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
6 // Copyright (C) 2014 Matthew LAGOE (Botanic) <cyberempires@gmail.com>
7 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
9 // This program is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU Affero General Public License as
11 // published by the Free Software Foundation, either version 3 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU Affero General Public License for more details.
19 // You should have received a copy of the GNU Affero General Public License
20 // along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "nel/misc/path.h"
26 #include "nel/misc/i18n.h"
31 #include <CoreFoundation/CoreFoundation.h>
42 CI18N::StrMapContainer
CI18N::_StrMap
;
43 CI18N::StrMapContainer
CI18N::_StrMapFallback
;
44 CI18N::StrMapContainer16
CI18N::_StrMap16
;
45 CI18N::StrMapContainer16
CI18N::_StrMapFallback16
;
46 bool CI18N::_StrMapLoaded
= false;
47 const ucstring
CI18N::_NotTranslatedValue16("<Not Translated>");
48 const std::string
CI18N::_NotTranslatedValue("<Not Translated>");
49 bool CI18N::_LanguagesNamesLoaded
= false;
50 string
CI18N::_SelectedLanguageCode
;
51 CI18N::ILoadProxy
*CI18N::_LoadProxy
= 0;
52 vector
<string
> CI18N::_LanguageCodes
;
53 vector
<std::string
> CI18N::_LanguageNames
;
54 std::string
CI18N::_SystemLanguageCode
;
55 bool CI18N::noResolution
= false;
57 void CI18N::setLoadProxy(ILoadProxy
*loadProxy
)
59 _LoadProxy
= loadProxy
;
62 void CI18N::initLanguages()
64 if (!_LanguagesNamesLoaded
)
66 _LanguageCodes
.push_back("en");
67 _LanguageCodes
.push_back("fr");
68 _LanguageCodes
.push_back("de");
69 _LanguageCodes
.push_back("ru");
70 _LanguageCodes
.push_back("es");
72 _LanguageNames
.push_back("English");
73 _LanguageNames
.push_back("French");
74 _LanguageNames
.push_back("German");
75 _LanguageNames
.push_back("Russian");
76 _LanguageNames
.push_back("Spanish");
78 _LanguagesNamesLoaded
= true;
82 const std::vector
<std::string
> &CI18N::getLanguageNames()
86 return _LanguageNames
;
89 const std::vector
<std::string
> &CI18N::getLanguageCodes()
93 return _LanguageCodes
;
96 void CI18N::load (const string
&languageCode
, const string
&fallbackLanguageCode
)
105 _StrMapLoaded
= true;
107 _SelectedLanguageCode
= languageCode
;
108 loadFileIntoMap(languageCode
+ ".uxt", _StrMap
, _StrMap16
);
110 _StrMapFallback
.clear();
111 _StrMapFallback16
.clear();
112 if(!fallbackLanguageCode
.empty())
114 loadFileIntoMap(fallbackLanguageCode
+ ".uxt", _StrMapFallback
, _StrMapFallback16
);
118 bool CI18N::loadFileIntoMap(const string
&fileName
, StrMapContainer
&destMap
, StrMapContainer16
&destMap16
)
123 _LoadProxy
->loadStringFile(fileName
, text
);
125 readTextFile(fileName
, text
);
127 // remove any comment
128 removeCComment(text
);
130 ucstring::const_iterator
first(text
.begin()), last(text
.end());
131 string
lastReadLabel("nothing");
133 while (first
!= last
)
135 skipWhiteSpace(first
, last
);
138 if (!parseLabel(first
, last
, label
))
140 nlwarning("I18N: Error reading label field in %s. Stop reading after %s.", fileName
.c_str(), lastReadLabel
.c_str());
143 lastReadLabel
= label
;
144 skipWhiteSpace(first
, last
);
145 if (!parseMarkedString('[', ']', first
, last
, ucs
))
147 nlwarning("I18N: Error reading text for label %s in %s. Stop reading.", label
.c_str(), fileName
.c_str());
152 pair
<map
<string
, ucstring
>::iterator
, bool> ret
;
153 ret
= destMap16
.insert(make_pair(label
, ucs
));
156 nlwarning("I18N: Error in %s, the label %s exists twice !", fileName
.c_str(), label
.c_str());
158 destMap
.insert(make_pair(label
, ucs
.toUtf8()));
159 skipWhiteSpace(first
, last
);
162 // a little check to ensure that the lang name has been set.
163 StrMapContainer::iterator
it(destMap
.find("LanguageName"));
164 if (it
== destMap
.end())
166 nlwarning("I18N: In file %s, missing LanguageName translation (should be first in file)", fileName
.c_str());
168 nlassert(destMap
.size() == destMap16
.size());
172 void CI18N::loadFromFilename(const string
&filename
, bool reload
)
174 StrMapContainer destMap
;
175 StrMapContainer16 destMap16
;
176 if (!loadFileIntoMap(filename
, destMap
, destMap16
))
180 // merge with existing map
181 for(StrMapContainer::iterator it
= destMap
.begin(); it
!= destMap
.end(); ++it
)
185 if (_StrMap16
.count(it
->first
))
187 nlwarning("I18N: Error in %s, the label %s exist twice !", filename
.c_str(), it
->first
.c_str());
190 _StrMap16
[it
->first
] = ucstring::makeFromUtf8(it
->second
);
191 _StrMap
[it
->first
] = it
->second
;
195 const std::string
&CI18N::get(const string
&label
)
204 static const std::string empty
;
208 StrMapContainer::iterator
it(_StrMap
.find(label
));
210 if (it
!= _StrMap
.end())
213 static CHashSet
<string
> missingStrings
;
214 if (missingStrings
.find(label
) == missingStrings
.end())
216 nlwarning("I18N: The string %s did not exist in language %s (display once)", label
.c_str(), _SelectedLanguageCode
.c_str());
217 missingStrings
.insert(label
);
220 // use the fall back language if it exists
221 it
= _StrMapFallback
.find(label
);
222 if (it
!= _StrMapFallback
.end())
225 static std::string badString
;
226 badString
= string("<NotExist:") + label
+ ">";
230 const ucstring
&CI18N::getAsUtf16 (const string
&label
)
234 static ucstring labelString
;
241 static const ucstring emptyString
;
245 StrMapContainer16::iterator
it(_StrMap16
.find(label
));
247 if (it
!= _StrMap16
.end())
250 static CHashSet
<string
> missingStrings
;
251 if (missingStrings
.find(label
) == missingStrings
.end())
253 nlwarning("I18N: The string %s did not exist in language %s (display once)", label
.c_str(), _SelectedLanguageCode
.c_str());
254 missingStrings
.insert(label
);
257 // use the fall back language if it exists
258 it
= _StrMapFallback16
.find(label
);
259 if (it
!= _StrMapFallback16
.end())
262 static ucstring badString
;
264 badString
= ucstring(string("<NotExist:")+label
+">");
269 bool CI18N::hasTranslation(const string
&label
)
271 if (label
.empty()) return true;
273 if(_StrMap
.find(label
) != _StrMap
.end())
276 // use the fall back language if it exists
277 if (_StrMapFallback
.find(label
) != _StrMapFallback
.end())
283 std::string
CI18N::getCurrentLanguageName ()
285 return get("LanguageName");
288 string
CI18N::getCurrentLanguageCode ()
290 return _SelectedLanguageCode
;
293 bool CI18N::isLanguageCodeSupported(const std::string
&lang
)
297 for (sint i
= 0, ilen
= _LanguageCodes
.size(); i
< ilen
; ++i
)
299 if (lang
== _LanguageCodes
[i
]) return true;
305 std::string
CI18N::getSystemLanguageCode ()
307 if (!_SystemLanguageCode
.empty())
308 return _SystemLanguageCode
;
311 // under OS X, locale is only defined in console, not in UI
312 // so we need to use CoreFoundation API to retrieve it
314 // get an array with all preferred languages
315 CFArrayRef langs
= CFLocaleCopyPreferredLanguages();
319 // get languages count
320 sint languagesCount
= CFArrayGetCount(langs
);
322 // process each language
323 for (sint i
= 0; i
< languagesCount
; ++i
)
327 // get language CFString
328 CFStringRef langCF
= (CFStringRef
)CFArrayGetValueAtIndex(langs
, i
);
332 // get a C string from CFString
333 const char *langStr
= CFStringGetCStringPtr(langCF
, kCFStringEncodingASCII
);
337 // get length of the CFString
338 CFIndex length
= CFStringGetLength(langCF
);
340 // allocate a temporary buffer to hold converted string
341 char *tmp
= new char[length
+1];
343 // use alternative function to get a C string from CFString
344 if (CFStringGetCString(langCF
, tmp
, length
+1, kCFStringEncodingASCII
))
346 lang
= std::string(tmp
, length
);
350 nlwarning("Unable to convert CFStringRef to string");
357 lang
= std::string(langStr
);
365 // fix language code if country is specified
366 std::string::size_type pos
= lang
.find('-');
368 if (pos
!= std::string::npos
)
369 lang
= lang
.substr(0, pos
);
371 // only keep language code if supported by NeL
372 if (isLanguageCodeSupported(lang
))
374 _SystemLanguageCode
= lang
;
380 // don't need languages array anymore
386 // use user locale under Windows (since Vista)
387 if (_SystemLanguageCode
.empty())
389 // GetUserDefaultLocaleName prototype
390 typedef int (WINAPI
* GetUserDefaultLocaleNamePtr
)(LPWSTR lpLocaleName
, int cchLocaleName
);
392 // get pointer on GetUserDefaultLocaleName, kernel32.dll is always in memory so no need to call LoadLibrary
393 HMODULE hKernel32
= GetModuleHandleA("kernel32.dll");
396 GetUserDefaultLocaleNamePtr nlGetUserDefaultLocaleName
= (GetUserDefaultLocaleNamePtr
)GetProcAddress(hKernel32
, "GetUserDefaultLocaleName");
398 // only use it if found
399 if (nlGetUserDefaultLocaleName
)
402 wchar_t buffer
[LOCALE_NAME_MAX_LENGTH
];
403 sint res
= nlGetUserDefaultLocaleName(buffer
, LOCALE_NAME_MAX_LENGTH
);
405 // convert wide string to std::string
406 std::string lang
= wideToUtf8(buffer
);
408 // only keep 2 first characters
410 _SystemLanguageCode
= lang
.substr(0, 2);
416 // use system locale (works under OS X, Linux and Windows)
417 if (_SystemLanguageCode
.empty())
419 // get default locale
420 char *locale
= setlocale(LC_CTYPE
, "");
424 std::string
lang(locale
);
427 // be sure supported languages are initialized
430 // locales names are different under Windows, for example: French_France.1252
431 for(uint i
= 0; i
< _LanguageNames
.size(); ++i
)
433 std::string name
= _LanguageNames
[i
];
435 // so we compare the language name with the supported ones
436 if (lang
.compare(0, name
.length(), name
) == 0)
438 // found, so use its code
439 _SystemLanguageCode
= _LanguageCodes
[i
];
446 // only keep 2 first characters
447 lang
= NLMISC::toLowerAscii(lang
).substr(0, 2);
449 // language code supported?
450 if (isLanguageCodeSupported(lang
))
451 _SystemLanguageCode
= lang
;
457 // english is default language
458 if (_SystemLanguageCode
.empty())
459 _SystemLanguageCode
= "en";
461 return _SystemLanguageCode
;
464 bool CI18N::setSystemLanguageCode (const std::string
&languageCode
)
466 // be sure supported languages are initialized
469 std::string lang
= NLMISC::toLowerAscii(languageCode
);
471 // specified language is really a code (2 characters)
472 if (lang
.length() == 2)
474 // check if language code is supported
475 for(uint i
= 0; i
< _LanguageCodes
.size(); ++i
)
477 std::string code
= NLMISC::toLowerAscii(_LanguageCodes
[i
]);
482 _SystemLanguageCode
= lang
;
487 // specified language is something else
490 // check if language name is supported
491 for(uint i
= 0; i
< _LanguageNames
.size(); ++i
)
493 std::string name
= NLMISC::toLowerAscii(_LanguageNames
[i
]);
497 // found, so use its code
498 _SystemLanguageCode
= _LanguageCodes
[i
];
507 void CI18N::removeCComment(ucstring
&commentedString
)
510 temp
.reserve(commentedString
.size());
511 ucstring::const_iterator
first(commentedString
.begin()), last(commentedString
.end());
512 for (;first
!= last
; ++first
)
514 temp
.push_back(*first
);
517 // no comment inside string literal
518 while (++first
!= last
)
520 temp
.push_back(*first
);
525 else if (*first
== '/')
527 // start of comment ?
529 if (first
!= last
&& *first
== '/')
531 temp
.resize(temp
.size()-1);
532 // one line comment, skip until end of line
533 while (first
!= last
&& *first
!= '\n')
536 else if (first
!= last
&& *first
== '*')
538 temp
.resize(temp
.size()-1);
539 // start of multi line comment, skip until we found '*/'
540 while (first
!= last
&& !(*first
== '*' && (first
+1) != last
&& *(first
+1) == '/'))
542 // skip the closing '/'
548 temp
.push_back(*first
);
552 commentedString
.swap(temp
);
555 void CI18N::skipWhiteSpace(ucstring::const_iterator
&it
, ucstring::const_iterator
&last
, ucstring
*storeComments
, bool newLineAsWhiteSpace
)
559 (*it
== 0xa && newLineAsWhiteSpace
)
560 || (*it
== 0xd && newLineAsWhiteSpace
)
563 || (storeComments
&& *it
== '/' && it
+1 != last
&& *(it
+1) == '/')
564 || (storeComments
&& *it
== '/' && it
+1 != last
&& *(it
+1) == '*')
567 if (storeComments
&& *it
== '/' && it
+1 != last
&& *(it
+1) == '/')
569 // found a one line C comment. Store it until end of line.
570 while (it
!= last
&& (*it
!= '\n' && *it
!= '\r'))
571 storeComments
->push_back(*it
++);
573 // store the final '\n'
575 storeComments
->push_back('\n');
577 else if (storeComments
&& *it
== '/' && it
+1 != last
&& *(it
+1) == '*')
579 // found a multi line C++ comment. store until we found the closing '*/'
580 while (it
!= last
&& !(*it
== '*' && it
+ 1 != last
&& *(it
+ 1) == '/'))
590 storeComments
->push_back(*it
++);
594 // store the final '*'
596 storeComments
->push_back(*it
++);
598 // store the final '/'
600 storeComments
->push_back(*it
++);
603 storeComments
->push_back('\n');
607 // just skip white space or don't store comments
613 bool CI18N::parseLabel(ucstring::const_iterator
&it
, ucstring::const_iterator
&last
, string
&label
)
615 ucstring::const_iterator rewind
= it
;
618 // first char must be A-Za-z@_
621 (*it
>= '0' && *it
<= '9')
622 || (*it
>= 'A' && *it
<= 'Z')
623 || (*it
>= 'a' && *it
<= 'z')
628 label
.push_back(char(*it
++));
635 // other char must be [0-9A-Za-z@_]*
638 (*it
>= '0' && *it
<= '9')
639 || (*it
>= 'A' && *it
<= 'Z')
640 || (*it
>= 'a' && *it
<= 'z')
645 label
.push_back(char(*it
++));
650 bool CI18N::parseMarkedString(ucchar openMark
, ucchar closeMark
, ucstring::const_iterator
&it
, ucstring::const_iterator
&last
, ucstring
&result
, uint32
*lineCounter
, bool allowNewline
)
654 // parse a string delimited by the specified opening and closing mark
655 if (it
!= last
&& *it
== openMark
)
659 while (it
!= last
&& *it
!= closeMark
&& (allowNewline
|| *it
!= '\n'))
661 // ignore tab, new lines and line feed
664 nlwarning("I18N: Found a non escaped openmark %c in a delimited string (Delimiters : '%c' - '%c')", char(openMark
), char(openMark
), char(closeMark
));
668 || (*it
== '\n' && allowNewline
)
671 else if (*it
== '\\' && it
+1 != last
&& *(it
+1) != '\\')
674 // this is an escape sequence !
678 result
.push_back('\t');
681 result
.push_back('\n');
688 // escape the close mark ?
690 result
.push_back(closeMark
);
691 // escape the open mark ?
692 else if(*it
== openMark
)
693 result
.push_back(openMark
);
696 nlwarning("I18N: Ignoring unknown escape code \\%c (char value : %u)", char(*it
), *it
);
702 else if (*it
== '\\' && it
+1 != last
&& *(it
+1) == '\\')
706 result
.push_back(*it
);
711 if (*it
== '\n' && lineCounter
!= NULL
)
712 // update line counter
715 result
.push_back(*it
++);
719 if (it
== last
|| *it
!= closeMark
)
721 nlwarning("I18N: Missing end of delimited string (Delimiters : '%c' - '%c')", char(openMark
), char(closeMark
));
729 nlwarning("I18N: Malformed or non existent delimited string (Delimiters : '%c' - '%c')", char(openMark
), char(closeMark
));
736 void CI18N::readTextFile(const string
&filename
,
741 bool warnIfIncludesNotFound
)
743 // create the read context
744 TReadContext readContext
;
746 // call the inner function
747 _readTextFile(filename
, result
, fileLookup
, preprocess
, lineFmt
, warnIfIncludesNotFound
, readContext
);
749 if (!readContext
.IfStack
.empty())
751 nlwarning("Preprocess: Missing %u closing #endif after parsing %s", (uint
)readContext
.IfStack
.size(), filename
.c_str() );
755 bool CI18N::matchToken(const char* token
, ucstring::const_iterator
&it
, ucstring::const_iterator end
)
757 ucstring::const_iterator rewind
= it
;
758 skipWhiteSpace(it
, end
, NULL
, false);
759 while (it
!= end
&& *token
!= 0 && *it
== *token
)
776 void CI18N::skipLine(ucstring::const_iterator
&it
, ucstring::const_iterator end
, uint32
&lineCounter
)
778 while (it
!= end
&& *it
!= '\n')
788 void CI18N::_readTextFile(const string
&filename
,
793 bool warnIfIncludesNotFound
,
794 TReadContext
&readContext
)
798 fullName
= CPath::lookup(filename
, false,warnIfIncludesNotFound
);
802 if (fullName
.empty())
805 // If ::lookup is used, the file can be in a bnp and CFile::fileExists fails.
806 bool isInBnp
= fullName
.find('@') != string::npos
;
807 if (!isInBnp
&& !CFile::fileExists(fullName
))
809 nlwarning("CI18N::readTextFile : file '%s' does not exist, returning empty string", fullName
.c_str());
813 NLMISC::CIFile
file(fullName
);
815 // Fast read all the text in binary mode.
817 text
.resize(file
.getFileSize());
818 if (file
.getFileSize() > 0)
819 file
.serialBuffer((uint8
*)(&text
[0]), (uint
)text
.size());
821 // Transform the string in ucstring according to format header
823 readTextBuffer((uint8
*)&text
[0], (uint
)text
.size(), result
);
827 // a string to old the result of the preprocess
829 // make rooms to reduce allocation cost
830 final
.reserve(raiseToNextPowerOf2((uint
)result
.size()));
832 // parse the file, looking for preprocessor command.
833 ucstring::const_iterator
it(result
.begin()), end(result
.end());
835 // input line counter
836 uint32 currentLine
= 1;
838 // set the current file and line info
839 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
843 // remember the begin of the line
844 ucstring::const_iterator beginOfLine
= it
;
846 // advance in the line, looking for a preprocessor command
847 skipWhiteSpace(it
, end
, NULL
, false);
849 if (it
!= end
&& *it
== '#')
851 // skip the '#' symbol
853 // we found a preprocessor command !
854 skipWhiteSpace(it
, end
, NULL
, false);
856 if (matchToken("include", it
, end
))
858 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
860 // we have an include command
861 skipWhiteSpace(it
, end
, NULL
, false);
863 // read the file name between quote
867 if (!parseMarkedString(ucchar('\"'), ucchar('\"'), it
, end
, str
, ¤tLine
, false))
869 nlwarning("Preprocess: In file %s(%u) : Error parsing include file command", filename
.c_str(), currentLine
);
875 // ok, read the subfile
876 string subFilename
= str
.toString();
878 // check is file exist
879 if (!CFile::fileExists(subFilename
))
881 // look for the file relative to current file
882 subFilename
= CFile::getPath(filename
)+subFilename
;
883 if (!CFile::fileExists(subFilename
))
885 // the include file is not found, issue a warning
886 nlwarning("Preprocess: In file %s(%u) : Cannot include file '%s'",
887 filename
.c_str(), currentLine
,
888 str
.toString().c_str());
894 nlinfo("Preprocess: In file %s(%u) : Including '%s'",
895 filename
.c_str(), currentLine
,
896 subFilename
.c_str());
899 _readTextFile(subFilename
, inserted
, fileLookup
, preprocess
, lineFmt
, warnIfIncludesNotFound
, readContext
);
903 // advance to next line
904 skipLine(it
, end
, currentLine
);
905 // reset filename and line counter
906 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
909 else if (matchToken("optional", it
, end
))
911 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
913 // we have an optional include command
914 skipWhiteSpace(it
, end
, NULL
, false);
916 // read the file name between quote
920 if (!parseMarkedString('\"', '\"', it
, end
, str
, ¤tLine
, false))
922 nlwarning("Preprocess: In file %s(%u) : Error parsing optional file command", filename
.c_str(), currentLine
);
928 // ok, read the subfile
929 string subFilename
= str
.toString();
931 // check is file exist
932 if (!CFile::fileExists(subFilename
))
934 // look for the file relative to current file
935 subFilename
= CFile::getPath(filename
)+subFilename
;
936 if (!CFile::fileExists(subFilename
))
938 // not found but optional, only emit a debug log
939 // the include file is not found, issue a warning
940 nldebug("Preprocess: In file %s(%u) : Cannot include optional file '%s'",
941 filename
.c_str(), currentLine
,
942 str
.toString().c_str());
948 nlinfo("Preprocess: In file %s(%u) : Including optional '%s'",
949 filename
.c_str(), currentLine
,
950 subFilename
.c_str());
953 _readTextFile(subFilename
, inserted
, fileLookup
, preprocess
, lineFmt
, warnIfIncludesNotFound
, readContext
);
957 // advance to next line
958 skipLine(it
, end
, currentLine
);
959 // reset filename and line counter
960 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
963 else if (matchToken("define", it
, end
))
965 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
967 skipWhiteSpace(it
, end
, NULL
, false);
970 if (parseLabel(it
, end
, label
))
972 if (readContext
.Defines
.find(label
) != readContext
.Defines
.end())
974 nlinfo("Preprocess: In file %s(%u) : symbol '%s' already defined",
975 filename
.c_str(), currentLine
,
980 readContext
.Defines
.insert(label
);
985 nlwarning("Preprocess: In file %s(%u) : Error parsing #define command", filename
.c_str(), currentLine
);
988 // advance to next line
989 skipLine(it
, end
, currentLine
);
990 // update filename and line number
991 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
994 else if (matchToken("ifdef", it
, end
))
996 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
998 skipWhiteSpace(it
, end
, NULL
, false);
1000 if (parseLabel(it
, end
, label
))
1002 if (readContext
.Defines
.find(label
) != readContext
.Defines
.end())
1004 // symbol defined, push a true
1005 readContext
.IfStack
.push_back(true);
1009 // symbol not defines, push a false
1010 readContext
.IfStack
.push_back(false);
1015 nlwarning("Preprocess: In file %s(%u) : Error parsing #ifdef command", filename
.c_str(), currentLine
);
1018 // advance to next line
1019 skipLine(it
, end
, currentLine
);
1020 // update filename and line number
1021 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
1025 // just push to false
1026 readContext
.IfStack
.push_back(false);
1028 skipLine(it
, end
, currentLine
);
1031 else if (matchToken("ifndef", it
, end
))
1033 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
1035 skipWhiteSpace(it
, end
, NULL
, false);
1037 if (parseLabel(it
, end
, label
))
1039 if (readContext
.Defines
.find(label
) == readContext
.Defines
.end())
1041 // symbol defined, push a true
1042 readContext
.IfStack
.push_back(true);
1046 // symbol not defines, push a false
1047 readContext
.IfStack
.push_back(false);
1052 nlwarning("Preprocess: In file %s(%u) : Error parsing #ifndef command", filename
.c_str(), currentLine
);
1055 // advance to next line
1056 skipLine(it
, end
, currentLine
);
1057 // update filename and line number
1058 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
1062 // just push to false
1063 readContext
.IfStack
.push_back(false);
1065 skipLine(it
, end
, currentLine
);
1068 else if (matchToken("endif", it
, end
))
1070 bool previous
= false;
1071 if (readContext
.IfStack
.empty())
1073 nlwarning("Preprocess: In file %s(%u) : Error found '#endif' without matching #if", filename
.c_str(), currentLine
);
1077 previous
= readContext
.IfStack
.back();
1079 readContext
.IfStack
.pop_back();
1081 skipLine(it
, end
, currentLine
);
1083 if (!previous
&& (readContext
.IfStack
.empty() || readContext
.IfStack
.back()))
1085 // end of ignored file part, restore the file and line number
1086 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
1088 // update filename and line number
1089 // final += toString("#fileline \"%s\" %u\n", filename.c_str(), currentLine);
1093 // unrecognized command, ignore line
1094 nlwarning("Preprocess: In file %s(%u) : Error unrecognized preprocessor command",
1095 filename
.c_str(), currentLine
);
1097 skipLine(it
, end
, currentLine
);
1098 // update filename and line number
1099 final
+= toString("#fileline \"%s\" %u\n", filename
.c_str(), currentLine
);
1105 skipLine(it
, end
, currentLine
);
1107 if (readContext
.IfStack
.empty() || readContext
.IfStack
.back())
1109 // copy the line to the final string
1110 final
.append(beginOfLine
, it
);
1115 // set the result with the preprocessed content
1119 // apply line delimiter conversion if needed
1120 if (lineFmt
!= LINE_FMT_NO_CARE
)
1122 if (lineFmt
== LINE_FMT_LF
)
1125 // easy, just remove or replace any \r code
1126 string::size_type pos
;
1127 string::size_type lastPos
= 0;
1129 // reserve some place to reduce re-allocation
1130 temp
.reserve(result
.size() +result
.size()/10);
1132 // look for the first \r
1133 pos
= result
.find('\r');
1134 while (pos
!= string::npos
)
1136 if (pos
< result
.size()-1 && result
[pos
+1] == '\n')
1138 temp
.append(result
.begin()+lastPos
, result
.begin()+pos
);
1143 temp
.append(result
.begin()+lastPos
, result
.begin()+pos
);
1144 temp
[temp
.size()-1] = '\n';
1149 pos
= result
.find('\r', pos
);
1153 temp
.append(result
.begin()+lastPos
, result
.end());
1157 else if (lineFmt
== LINE_FMT_CRLF
)
1159 // need to replace simple '\n' or '\r' with a '\r\n' double
1160 string::size_type pos
= 0;
1161 string::size_type lastPos
= 0;
1164 // reserve some place to reduce re-allocation
1165 temp
.reserve(result
.size() +result
.size()/10);
1168 // first loop with the '\r'
1169 pos
= result
.find('\r', pos
);
1170 while (pos
!= string::npos
)
1172 if (pos
>= result
.size()-1 || result
[pos
+1] != '\n')
1174 temp
.append(result
.begin()+lastPos
, result
.begin()+pos
+1);
1181 // look the next '\r'
1182 pos
= result
.find('\r', pos
);
1186 temp
.append(result
.begin()+lastPos
, result
.end());
1191 // second loop with the '\n'
1194 pos
= result
.find('\n', pos
);
1195 while (pos
!= string::npos
)
1197 if (pos
== 0 || result
[pos
-1] != '\r')
1199 temp
.append(result
.begin()+lastPos
, result
.begin()+pos
);
1207 pos
= result
.find('\n', pos
);
1211 temp
.append(result
.begin()+lastPos
, result
.end());
1217 void CI18N::readTextBuffer(uint8
*buffer
, uint size
, ucstring
&result
)
1219 static uint8 utf16Header
[] = { 0xffu
, 0xfeu
};
1220 static uint8 utf16RevHeader
[] = { 0xfeu
, 0xffu
};
1221 static uint8 utf8Header
[] = { 0xefu
, 0xbbu
, 0xbfu
};
1224 buffer
[0]==utf8Header
[0] &&
1225 buffer
[1]==utf8Header
[1] &&
1226 buffer
[2]==utf8Header
[2]
1229 // remove utf8 header
1232 string
text((char*)buffer
, size
);
1233 result
.fromUtf8(text
);
1236 buffer
[0]==utf16Header
[0] &&
1237 buffer
[1]==utf16Header
[1]
1240 // remove utf16 header
1243 // check pair number of bytes
1244 nlassert((size
& 1) == 0);
1245 // and do manual conversion
1246 uint16
*src
= (uint16
*)(buffer
);
1247 result
.resize(size
/2);
1248 for (uint j
=0; j
<result
.size(); j
++)
1252 buffer
[0]==utf16RevHeader
[0] &&
1253 buffer
[1]==utf16RevHeader
[1]
1256 // remove utf16 header
1259 // check pair number of bytes
1260 nlassert((size
& 1) == 0);
1261 // and do manual conversion
1262 uint16
*src
= (uint16
*)(buffer
);
1263 result
.resize(size
/2);
1265 for (j
=0; j
<result
.size(); j
++)
1267 // Reverse byte order
1268 for (j
=0; j
<result
.size(); j
++)
1270 uint8
*pc
= (uint8
*) &result
[j
];
1276 // all text files without BOM are now parsed as UTF-8 by default
1277 string
text((char*)buffer
, size
);
1278 result
.fromUtf8(text
);
1282 void CI18N::writeTextFile(const string filename
, const ucstring
&content
, bool utf8
)
1284 COFile
file(filename
);
1288 // write the Unicode 16 bits tag
1289 uint16 unicodeTag
= 0xfeff;
1290 file
.serial(unicodeTag
);
1293 for (i
=0; i
<content
.size(); ++i
)
1295 uint16 c
= content
[i
];
1301 static char utf8Header
[] = {char(0xef), char(0xbb), char(0xbf), 0};
1303 string str
= encodeUTF8(content
);
1304 // add the UTF-8 'not official' header
1305 str
= utf8Header
+ str
;
1308 for (i
=0; i
<str
.size(); ++i
)
1310 file
.serial(str
[i
]);
1315 ucstring
CI18N::makeMarkedString(ucchar openMark
, ucchar closeMark
, const ucstring
&text
)
1319 ret
.push_back(openMark
);
1321 ucstring::const_iterator
first(text
.begin()), last(text
.end());
1322 for (; first
!= last
; ++first
)
1329 else if (*first
== '\t')
1334 else if (*first
== closeMark
)
1336 // escape the embedded closing mark
1351 string
CI18N::encodeUTF8(const ucstring
&str
)
1353 return str
.toUtf8();
1356 /* UTF-8 conversion table
1357 U-00000000 - U-0000007F: 0xxxxxxx
1358 U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
1359 U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
1360 // not used as we convert from 16 bits unicode
1361 U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1362 U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1363 U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1366 uint64
CI18N::makeHash(const ucstring
&str
)
1368 // we do at least 8 pass on each result byte
1371 const uint32 MIN_TURN
= 8*8;
1373 uint8
*ph
= (uint8
*)&hash
;
1374 uint8
*pc
= (uint8
*)str
.data();
1376 uint nbLoop
= max(uint32(str
.size()*2), MIN_TURN
);
1379 for (uint i
=0; i
<nbLoop
; ++i
)
1381 ph
[(i
/2) & 0x7] = uint8((ph
[(i
/2) & 0x7] + (pc
[i
%(str
.size()*2)] << roll
)) & 0xff);
1382 ph
[(i
/2) & 0x7] = uint8((ph
[(i
/2) & 0x7] + (pc
[i
%(str
.size()*2)] >> (8-roll
))) & 0xff);
1391 // convert a hash value to a readable string
1392 string
CI18N::hashToString(uint64 hash
)
1394 char temp
[] = "0011223344556677";
1395 sprintf(temp
, "%08X%08X", (uint32
)(hash
& 0xffffffff), (uint32
)(hash
>> 32));
1397 return string(temp
);
1400 // fast convert a hash value to a ucstring
1401 void CI18N::hashToUCString(uint64 hash
, ucstring
&dst
)
1403 static ucchar cvtTable
[]= {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
1406 for(sint i
=15;i
>=0;i
--)
1408 // Must decal dest of 8, cause of hashToString code (Little Endian)
1409 dst
[(i
+8)&15]= cvtTable
[hash
&15];
1414 // convert a readable string into a hash value.
1415 uint64
CI18N::stringToHash(const string
&str
)
1417 nlassert(str
.size() == 16);
1421 sh
= str
.substr(0, 8);
1422 sl
= str
.substr(8, 8);
1424 sscanf(sh
.c_str(), "%08X", &high
);
1425 sscanf(sl
.c_str(), "%08X", &low
);
1429 memcpy(&hash
, &high
, sizeof(high
));
1430 memcpy((uint32
*)&hash
+ 1, &low
, sizeof(low
));
1435 } // namespace NLMISC