200 lines
5.3 KiB
C++
200 lines
5.3 KiB
C++
/*
|
|
PIP - Platform Independent Primitives
|
|
Ivan Pelipenko peri4ko@yandex.ru
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "ts_file.h"
|
|
|
|
#include "pifile.h"
|
|
#include "piiostream.h"
|
|
|
|
|
|
void TSFile::Context::confirm(const PIString & msg, const PIString & file, int line) {
|
|
if (msg.isEmpty()) return;
|
|
bool is_new = !messages.contains(msg);
|
|
auto & m(messages[msg]);
|
|
m.source = msg;
|
|
m.filename = file;
|
|
m.line = line >= 0 ? PIString::fromNumber(line) : PIString();
|
|
m.obsolete = false;
|
|
if (is_new) m.type = "unfinished";
|
|
}
|
|
|
|
|
|
PIString TSFile::mask(const PIString & in) {
|
|
static const PIVector<PIPair<PIString, PIString>> map = {
|
|
{"&", "&" },
|
|
{"<", "<" },
|
|
{">", ">" },
|
|
{"'", "'"},
|
|
{"\"", """},
|
|
};
|
|
PIString ret = in;
|
|
for (const auto & i: map)
|
|
ret.replaceAll(i.first, i.second);
|
|
return ret;
|
|
}
|
|
|
|
|
|
PIString TSFile::unmask(const PIString & in) {
|
|
static const PIVector<PIPair<PIString, PIString>> map = {
|
|
{"<", "<" },
|
|
{">", ">" },
|
|
{"'", "'"},
|
|
{"\"", """},
|
|
{"&", "&" },
|
|
};
|
|
PIString ret = in;
|
|
for (const auto & i: map)
|
|
ret.replaceAll(i.second, i.first);
|
|
return ret;
|
|
}
|
|
|
|
|
|
TSFile::Content TSFile::read(const PIString & path) {
|
|
Content ret;
|
|
PIFile f(path, PIIODevice::ReadOnly);
|
|
if (!f.isOpened()) return ret;
|
|
PIIOTextStream ts(&f);
|
|
Context * cc = nullptr;
|
|
Message msg;
|
|
int phase = 0;
|
|
bool multi_source = false, multi_translation = false;
|
|
while (!ts.isEnd()) {
|
|
auto line = ts.readLine().trim();
|
|
if (multi_source) {
|
|
if (line.endsWith("</source>")) {
|
|
line.cutRight(9);
|
|
multi_source = false;
|
|
}
|
|
msg.source += "\n" + unmask(line);
|
|
continue;
|
|
}
|
|
if (multi_translation) {
|
|
if (line.endsWith("</translation>")) {
|
|
line.cutRight(14);
|
|
multi_translation = false;
|
|
}
|
|
msg.translation += "\n" + unmask(line);
|
|
continue;
|
|
}
|
|
switch (phase) {
|
|
case 0:
|
|
if (line == "<context>") phase = 1;
|
|
break;
|
|
case 1:
|
|
if (line.startsWith("<name>")) {
|
|
line.cutLeft(6).cutRight(7);
|
|
// if (line == context) phase = 2;
|
|
cc = &(ret[line]);
|
|
phase = 2;
|
|
}
|
|
break;
|
|
case 2:
|
|
if (line == "<message>") {
|
|
msg = {};
|
|
phase = 3;
|
|
}
|
|
break;
|
|
case 3:
|
|
if (line == "</message>") {
|
|
if (cc) cc->messages[msg.source] = msg;
|
|
phase = 2;
|
|
} else if (line.startsWith("<source>")) {
|
|
line.cutLeft(8);
|
|
if (line.endsWith("</source>"))
|
|
line.cutRight(9);
|
|
else
|
|
multi_source = true;
|
|
msg.source = unmask(line);
|
|
} else if (line.startsWith("<location")) {
|
|
PIString trs = line.takeRange('<', '>').cutLeft(8);
|
|
while (trs.isNotEmpty()) {
|
|
PIString t = trs.takeCWord();
|
|
trs.cutLeft(1);
|
|
PIString v = trs.takeRange('\"', '\"');
|
|
if (t == "filename") msg.filename = v;
|
|
if (t == "line") msg.line = v;
|
|
if (trs.size_s() <= 2) break;
|
|
}
|
|
} else if (line.startsWith("<translation")) {
|
|
PIString trs = line.takeRange('<', '>').cutLeft(11);
|
|
while (trs.isNotEmpty()) {
|
|
PIString t = trs.takeCWord();
|
|
trs.cutLeft(1);
|
|
PIString v = trs.takeRange('\"', '\"');
|
|
if (t == "type") msg.type = v;
|
|
}
|
|
if (line.endsWith("</translation>"))
|
|
line.cutRight(14);
|
|
else
|
|
multi_translation = true;
|
|
msg.translation = unmask(line);
|
|
}
|
|
break;
|
|
}
|
|
if (line == "</context>") {
|
|
cc = nullptr;
|
|
phase = 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool TSFile::write(const PIString & path, const TSFile::Content & content, const PIString & lang, bool no_obsolete) {
|
|
PIFile outf(path, PIIODevice::ReadWrite);
|
|
if (!outf.isOpened()) {
|
|
piCerr << "Can`t open" << outf.path() << "!";
|
|
return false;
|
|
}
|
|
outf.clear();
|
|
PIIOTextStream ts(&outf);
|
|
auto writeMessage = [&ts](const Message & m) {
|
|
if (m.source.isEmpty()) return;
|
|
ts << " <message>\n";
|
|
if (m.filename.isNotEmpty()) {
|
|
ts << " <location filename=\"" << m.filename << "\"";
|
|
if (m.line.isNotEmpty()) ts << " line=\"" << m.line << "\"";
|
|
ts << "/>\n";
|
|
}
|
|
ts << " <source>" << mask(m.source) << "</source>\n";
|
|
ts << " <translation";
|
|
if (m.type.isNotEmpty()) ts << " type=\"" << m.type << "\"";
|
|
ts << ">" << mask(m.translation) << "</translation>\n";
|
|
ts << " </message>\n";
|
|
};
|
|
|
|
ts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
|
ts << "<!DOCTYPE TS>\n";
|
|
ts << "<TS version=\"2.1\" language=\"" << lang << "\">\n";
|
|
auto cit = content.makeIterator();
|
|
while (cit.next()) {
|
|
if (cit.value().messages.isEmpty()) continue;
|
|
ts << "<context>\n";
|
|
ts << " <name>" << cit.key() << "</name>\n";
|
|
auto mit = cit.value().messages.makeIterator();
|
|
while (mit.next()) {
|
|
if (mit.value().obsolete && no_obsolete) continue;
|
|
writeMessage(mit.value());
|
|
}
|
|
ts << "</context>\n";
|
|
};
|
|
ts << "</TS>\n";
|
|
|
|
return true;
|
|
}
|