Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/src/LevelPackPOTExporter.cpp b/src/LevelPackPOTExporter.cpp
index 8099eb0..d402c8c 100644
--- a/src/LevelPackPOTExporter.cpp
+++ b/src/LevelPackPOTExporter.cpp
@@ -1,499 +1,504 @@
/*
* Copyright (C) 2019 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPackPOTExporter.h"
#include "Functions.h"
#include "FileManager.h"
#include "POASerializer.h"
#include "TreeStorageNode.h"
#include "FakeLuaLexer.h"
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <algorithm>
#include <iostream>
struct POTFileEntry {
std::string msgid, msgid_plural;
std::string comments, sources;
};
//Check if a string contains c-format specifier.
//Currently we only support %[+-]?[0-9]*[.]?[0-9]*[diufFeEgGaAxXoscp]
static bool isCFormat(const std::string& s) {
for (int i = 0, m = s.size(); i < m; i++) {
if (s[i] == '%') {
i++;
if (i < m) {
switch (s[i]) {
case '+': case '-':
i++;
break;
}
}
int dotCount = 0;
while (i < m) {
switch (s[i]) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break;
case '.':
dotCount++;
break;
default:
dotCount = 2;
break;
}
if (dotCount >= 2) break;
i++;
}
if (i < m) {
switch (s[i]) {
case 'd': case 'i': case 'u': case 'f': case 'F':
case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
case 'x': case 'X': case 'o': case 's': case 'c': case 'p':
return true;
}
}
}
}
return false;
}
static void writeComment(std::ostream& fout, const std::string& comments, const std::string& prefix) {
std::string message;
for (auto c : comments) {
if (c != '\r') message.push_back(c);
}
//Trim the message.
{
size_t lps = message.find_first_not_of('\n'), lpe = message.find_last_not_of("\n \t");
if (lps == std::string::npos || lpe == std::string::npos || lps > lpe) {
message.clear(); // it's completely empty
} else {
message = message.substr(lps, lpe - lps + 1);
}
}
if (!message.empty()) {
message.push_back('\0');
//Split the message into lines.
for (int lps = 0;;) {
// determine the end of line
int lpe = lps;
for (; message[lpe] != '\n' && message[lpe] != '\0'; lpe++);
// output the line
fout << prefix << message.substr(lps, lpe - lps) << std::endl;
// break if the string ends
if (message[lpe] == '\0') break;
// point to the start of next line
lps = lpe + 1;
}
}
}
static void writeHeader(std::ostream& fout) {
time_t rawtime;
struct tm *timeinfo;
char buffer[256];
time(&rawtime);
timeinfo = gmtime(&rawtime);
//FIXME: I can't make %z print timezone under Windows
strftime(buffer, sizeof(buffer), "\"POT-Creation-Date: %Y-%m-%d %H:%M+0000\\n\"", timeinfo);
fout << "# SOME DESCRIPTIVE TITLE." << std::endl;
fout << "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER" << std::endl;
fout << "# This file is distributed under the same license as the PACKAGE package." << std::endl;
fout << "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR." << std::endl;
fout << "#" << std::endl;
fout << "#, fuzzy" << std::endl;
fout << "msgid \"\"" << std::endl;
fout << "msgstr \"\"" << std::endl;
fout << "\"Project-Id-Version: PACKAGE VERSION\\n\"" << std::endl;
fout << "\"Report-Msgid-Bugs-To: \\n\"" << std::endl;
fout << buffer << std::endl;
fout << "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"" << std::endl;
fout << "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"" << std::endl;
fout << "\"Language-Team: LANGUAGE <LL@li.org>\\n\"" << std::endl;
fout << "\"Language: \\n\"" << std::endl;
fout << "\"MIME-Version: 1.0\\n\"" << std::endl;
fout << "\"Content-Type: text/plain; charset=UTF-8\\n\"" << std::endl;
fout << "\"Content-Transfer-Encoding: 8bit\\n\"" << std::endl;
fout << "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"" << std::endl;
fout << std::endl;
}
class POTFileEntries {
public:
std::vector<POTFileEntry> entries;
std::map<std::string, int> lookupTable;
public:
//Add a msgid to the entries.
void addEntry(const std::string& msgid, const std::string& msgid_plural, const std::string& comments, const std::string& sources) {
if (msgid.empty()) return;
int index;
{
std::string key = msgid + "\x01" + msgid_plural;
auto it = lookupTable.find(key);
if (it == lookupTable.end()) {
entries.emplace_back();
lookupTable[key] = index = entries.size() - 1;
entries[index].msgid = msgid;
entries[index].msgid_plural = msgid_plural;
} else {
index = it->second;
}
}
if (!comments.empty()) {
if (comments.back() == '\n') {
entries[index].comments += comments;
} else {
entries[index].comments += comments + "\n";
}
}
if (!sources.empty()) {
if (sources.back() == '\n') {
entries[index].sources += sources;
} else {
entries[index].sources += sources + "\n";
}
}
}
//Write all the entries to file.
void writeEntry(std::ostream& fout) {
for (auto &entry : entries) {
+ //Check if the msgid contains special format strings.
+ if (entry.msgid.find("{{{") != std::string::npos || entry.msgid.find("}}}") != std::string::npos) {
+ entry.comments += "TRANSLATORS: Please keep words between '{{{' and '}}}' untranslated.\n";
+ }
+
//Write comments.
writeComment(fout, entry.comments, "# ");
//Write sources.
writeComment(fout, entry.sources, "#: ");
//Check if it's c-format.
if (isCFormat(entry.msgid) || isCFormat(entry.msgid_plural)) {
fout << "#, c-format" << std::endl;
}
//Write msgids.
fout << "msgid \"" << escapeCString(entry.msgid) << "\"" << std::endl;
if (entry.msgid_plural.empty()) {
fout << "msgstr \"\"" << std::endl;
} else {
fout << "msgid_plural \"" << escapeCString(entry.msgid_plural) << "\"" << std::endl;
fout << "msgstr[0] \"\"" << std::endl;
fout << "msgstr[1] \"\"" << std::endl;
}
//Add a newline.
fout << std::endl;
}
}
};
static std::string formatSource(const std::string& fileName, const ITreeStorageBuilder::FilePosition& pos, bool showColumn = false) {
char s[32];
if (showColumn) {
sprintf(s, ":%d:%d", pos.row, pos.column);
} else {
sprintf(s, ":%d", pos.row);
}
return fileName + s;
}
class LoadLevelListTreeStorageNode : public TreeStorageNode {
public:
POTFileEntries *pot;
public:
LoadLevelListTreeStorageNode(POTFileEntries *pot) : TreeStorageNode(), pot(pot) {}
virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos) override {
//Do our own stuff first.
if (name == "name" && value.size() >= 1) {
pot->addEntry(value[0], "",
"TRANSLATORS: This is the name of the level pack.",
formatSource("levels.lst", valuePos[0]));
} else if (name == "description" && value.size() >= 1) {
pot->addEntry(value[0], "",
"TRANSLATORS: This is the description of the level pack.",
formatSource("levels.lst", valuePos[0]));
} else if (name == "congratulations" && value.size() >= 1) {
pot->addEntry(value[0], "",
"TRANSLATORS: This will be shown when all the levels in the pack are finished.",
formatSource("levels.lst", valuePos[0]));
}
//Do default stuff.
return TreeStorageNode::newAttribute(name, value, namePos, valuePos);
}
};
class FakeLuaParser {
public:
POTFileEntries *pot;
FakeLuaLexer& lexer;
const std::string& fileName;
public:
FakeLuaParser(POTFileEntries *pot, FakeLuaLexer& lexer, const std::string& fileName)
: pot(pot), lexer(lexer), fileName(fileName)
{
}
void parse() {
comment.clear();
bool skipOnce = false;
for (;;) {
if (skipOnce) skipOnce = false;
else if (!getNextNonCommentToken()) return;
// we only parse the following format
// ( '_' | '__' | 'gettext' ) ( <string> | '(' <string> ')' )
// or
// 'ngettext' '(' <string> ',' <string> ','
if (lexer.tokenType == FakeLuaLexer::Identifier) {
if (lexer.token == "_" || lexer.token == "__" || lexer.token == "gettext") {
if (!getNextNonCommentToken()) return;
std::string msgid;
ITreeStorageBuilder::FilePosition pos;
if (lexer.tokenType == FakeLuaLexer::StringLiteral) {
msgid = lexer.token;
pos = lexer.posStart;
} else if (lexer.tokenType == FakeLuaLexer::Operator && lexer.token == "(") {
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::StringLiteral) {
msgid = lexer.token;
pos = lexer.posStart;
} else {
skipOnce = true;
continue;
}
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::Operator && lexer.token == ")") {
// do nothing
} else {
skipOnce = true;
continue;
}
} else {
skipOnce = true;
continue;
}
pot->addEntry(msgid, "", comment, formatSource(fileName, pos));
comment.clear();
} else if (lexer.token == "ngettext") {
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::Operator && lexer.token == "(") {
// do nothing
} else {
skipOnce = true;
continue;
}
std::string msgid, msgid_plural;
ITreeStorageBuilder::FilePosition pos;
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::StringLiteral) {
msgid = lexer.token;
pos = lexer.posStart;
} else {
skipOnce = true;
continue;
}
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::Operator && lexer.token == ",") {
// do nothing
} else {
skipOnce = true;
continue;
}
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::StringLiteral) {
msgid_plural = lexer.token;
} else {
skipOnce = true;
continue;
}
if (!getNextNonCommentToken()) return;
if (lexer.tokenType == FakeLuaLexer::Operator && lexer.token == ",") {
// do nothing
} else {
skipOnce = true;
continue;
}
pot->addEntry(msgid, msgid_plural, comment, formatSource(fileName, pos));
comment.clear();
}
}
}
}
bool getNextNonCommentToken() {
bool isTranslatorsComment = false;
for (;;) {
if (!lexer.getNextToken()) {
if (!lexer.error.empty()) {
//Show error message.
std::cerr << formatSource(fileName, lexer.pos, true) << ": ERROR: " << lexer.error << std::endl;
}
return false;
}
if (lexer.tokenType == FakeLuaLexer::Comment) {
if (lexer.token.empty() || lexer.token.back() != '\n') lexer.token.push_back('\n');
if (!isTranslatorsComment) {
if (lexer.token.find("TRANSLATORS:") != std::string::npos) {
isTranslatorsComment = true;
comment.clear(); // remove last translators comment
}
}
if (isTranslatorsComment) comment += lexer.token;
} else {
return true;
}
}
}
private:
std::string comment;
};
class LoadLevelMessageTreeStorageNode : public TreeStorageNode {
public:
POTFileEntries *pot;
const std::string& fileName;
public:
LoadLevelMessageTreeStorageNode(POTFileEntries *pot, const std::string& fileName) : TreeStorageNode(), pot(pot), fileName(fileName) {}
virtual ITreeStorageBuilder* newNode() override {
return new LoadLevelMessageTreeStorageNode(pot, fileName);
}
virtual bool newAttribute(const std::string& name, const std::vector<std::string>& value, const FilePosition& namePos, const std::vector<FilePosition>& valuePos) override {
//Do our own stuff first.
if (name == "name" && value.size() >= 1) {
pot->addEntry(value[0], "",
"TRANSLATORS: This is the name of a level.",
formatSource(fileName, valuePos[0]));
} else if (name == "message" && value.size() >= 1) {
if (this->name == "tile" && this->value.size() >= 1 && this->value[0] == "NotificationBlock") {
pot->addEntry(unescapeNewline(value[0]), "", "",
formatSource(fileName, valuePos[0]));
} else {
pot->addEntry(value[0], "", "",
formatSource(fileName, valuePos[0]));
}
} else if (name == "script" && value.size() >= 1) {
//Now we extract strings from script.
FakeLuaLexer lexer;
lexer.buf = value[0].c_str();
lexer.pos = valuePos[0];
lexer.storedByPOASerializer = true;
FakeLuaParser parser(pot, lexer, fileName);
parser.parse();
}
//Do default stuff.
return TreeStorageNode::newAttribute(name, value, namePos, valuePos);
}
};
bool LevelPackPOTExporter::exportPOT(const std::string& levelpackPath) {
//Open the level list.
std::string levelListFile = levelpackPath + "levels.lst";
std::ifstream fin(levelListFile.c_str());
if (!fin) {
std::cerr << "ERROR: Can't load level list " << levelListFile << std::endl;
return false;
}
//Create the entries.
POTFileEntries pot;
//Load the level list file.
LoadLevelListTreeStorageNode obj(&pot);
if (!POASerializer().readNode(fin, &obj, true)){
std::cerr << "ERROR: Invalid file format of level list " << levelListFile << std::endl;
return false;
}
//Loop through the level list entries.
for (unsigned int i = 0; i<obj.subNodes.size(); i++){
TreeStorageNode* obj1 = obj.subNodes[i];
if (obj1 == NULL)
continue;
if (!obj1->value.empty() && obj1->name == "levelfile") {
std::string fileName = obj1->value[0];
//The path to the file to open.
std::string levelFile = levelpackPath + fileName;
//Open the level file.
LoadLevelMessageTreeStorageNode obj(&pot, fileName);
if (!POASerializer().loadNodeFromFile(levelFile.c_str(), &obj, true)) {
return false;
}
}
}
//Create the directory.
createDirectory((levelpackPath + "locale").c_str());
//Create the messages.pot
std::string potFile = levelpackPath + "locale/messages.pot";
std::ofstream fout(potFile.c_str());
if (!fout) {
std::cerr << "ERROR: Can't open the file " << potFile << " for save" << std::endl;
return false;
}
//Write entries.
writeHeader(fout);
pot.writeEntry(fout);
//Over.
return true;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 5:07 AM (5 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62941
Default Alt Text
(14 KB)

Event Timeline