1 /*********
2 *
3 * This file is part of BibleTime's source code, http://www.bibletime.info/.
4 *
5 * Copyright 1999-2017 by the BibleTime developers.
6 * The BibleTime source code is licensed under the GNU General Public License version 2.0.
7 *
8 **********/
9
10 #include <cassert>
11 #include <cstddef>
12 #include <cstdlib>
13 #include <iostream>
14 #include <QCoreApplication>
15 #include <QFile>
16 #include <QFileInfo>
17 #include <QRegularExpression>
18 #include <QSet>
19 #include <QStringList>
20 #include <QTextStream>
21 #include <QXmlStreamReader>
22 #include <QXmlStreamWriter>
23 #include <utility>
24
25
26 char const * inFilename;
27 QString inFilePath;
28
29 QRegularExpression const hasPotentialUnicodeEscape(
30 "u[0123456789abcdefABCDEF]{4}");
31 QRegularExpression const unicodeEscape("\\\\u[0123456789abcdefABCDEF]{4}");
32
33 #define PASS do { writer.writeCurrentToken(reader); } while (false)
34
35
checkUnicodeEscape(QString & source,QString const & locationFilename,std::size_t const locationLine)36 bool checkUnicodeEscape(QString & source,
37 QString const & locationFilename,
38 std::size_t const locationLine)
39 {
40 QFile inFile(inFilePath + '/' + locationFilename);
41 if (!inFile.open(QIODevice::ReadOnly)) {
42 std::cerr << inFilename << ": Failed to open source file: "
43 << locationFilename.toStdString() << std::endl;
44 return false;
45 }
46 QTextStream in(&inFile);
47 for (std::size_t line = 1u; line < locationLine; ++line)
48 if (in.readLine().isNull())
49 return false;
50 auto line(in.readLine());
51 if (line.isNull())
52 return false;
53 line = line.trimmed();
54
55 QStringList strings;
56 int start = 0;
57 for (;;) {
58 start = line.indexOf('"', start);
59 if (start < 0)
60 break;
61 ++start;
62 auto end = line.indexOf('"', start);
63 if (end < 0)
64 break;
65 if (start != end) {
66 auto str(line.mid(start, end - start));
67 strings.append(std::move(str));
68 }
69 start = end;
70 ++start;
71 }
72
73 for (;;) {
74 line = in.readLine();
75 if (line.isNull())
76 break;
77 line = line.trimmed();
78 if (line.isEmpty())
79 continue;
80 while (line.startsWith('"')) {
81 auto end = line.indexOf('"', 1);
82 if (end < 0)
83 goto finish;
84 if (end > 1) {
85 auto str(line.mid(1, end - 1));
86 strings.last().append(std::move(str));
87 }
88 line = line.mid(end + 1).trimmed();
89 }
90 if (!line.isEmpty())
91 break;
92 }
93
94 finish:
95
96 QSet<QString> rStrings;
97 for (auto const & str : strings) {
98 auto s(str);
99 s.replace("\\u", "u");
100 if (source == s)
101 rStrings.insert(str);
102 }
103
104 switch (rStrings.size()) {
105 case 0:
106 return false;
107 case 1:
108 {
109 source = *rStrings.begin();
110 int from = 0;
111 for (;;) {
112 int const i = source.indexOf(unicodeEscape, from);
113 if (i < 0)
114 break;
115 bool ok = false;
116 ushort const codePoint = source.mid(i + 2, 4).toUShort(&ok, 16);
117 assert(ok);
118 source = (source.left(i) + QChar(codePoint))
119 + source.mid(i + 6);
120 from = i + 1;
121 }
122 return true;
123 }
124 default:
125 std::cerr << inFilename << ": Strange strings in "
126 << locationFilename.toStdString() << ':' << locationLine
127 << std::endl;
128 return false;
129 }
130 if (rStrings.size() != 1)
131 return false;
132
133 }
134
readError(QXmlStreamReader & reader)135 void readError(QXmlStreamReader & reader) {
136 std::cerr << inFilename << ':' << reader.lineNumber() << ':'
137 << reader.columnNumber() << ": "
138 << reader.errorString().toStdString() << std::endl;
139 std::exit(4);
140 }
141
recurse(QXmlStreamReader & reader,QXmlStreamWriter & writer)142 void recurse(QXmlStreamReader & reader,
143 QXmlStreamWriter & writer)
144 {
145 std::size_t mud = 0u;
146 QString locationFilename;
147 unsigned long long locationLine = 0u;
148 QString source;
149 while (!reader.atEnd()) {
150 switch (reader.readNext()) {
151
152 case QXmlStreamReader::Invalid:
153 readError(reader);
154
155 case QXmlStreamReader::StartElement:
156 if (mud) {
157 ++mud;
158 PASS;
159 } else {
160 if (reader.qualifiedName() == "location") {
161 auto const attrs(reader.attributes());
162 locationFilename = attrs.value("filename").toString();
163 locationLine = attrs.value("line").toULongLong();
164 ++mud;
165 PASS;
166 } else if (reader.qualifiedName() == "source") {
167 PASS;
168 QString source(reader.readElementText());
169 if (source.contains(hasPotentialUnicodeEscape)) {
170 QString const oldSource(source);
171 if (!checkUnicodeEscape(source,
172 locationFilename,
173 locationLine))
174 {
175 std::cerr << inFilename << ": \""
176 << source.toStdString() << "\" ("
177 << locationFilename.toStdString() << ':'
178 << locationLine << ") not substituted!"
179 << std::endl;
180 assert(oldSource == source);
181 } else {
182 std::cerr << inFilename << ": \""
183 << oldSource.toStdString() << "\" -> \""
184 << source.toStdString() << "\" ("
185 << locationFilename.toStdString() << ':'
186 << locationLine << ") substituted."
187 << std::endl;
188 assert(oldSource != source);
189 }
190 }
191 writer.writeCharacters(source);
192 writer.writeEndElement();
193 } else {
194 ++mud;
195 PASS;
196 }
197 }
198 break;
199 case QXmlStreamReader::EndElement:
200 if (mud) {
201 --mud;
202 PASS;
203 break;
204 } else {
205 PASS;
206 return;
207 }
208 default:
209 PASS;
210 break;
211 };
212 }
213 }
214
215 template <typename ... Paths>
recurse(QXmlStreamReader & reader,QXmlStreamWriter & writer,char const * const path,Paths &&...paths)216 void recurse(QXmlStreamReader & reader,
217 QXmlStreamWriter & writer,
218 char const * const path,
219 Paths && ... paths)
220 {
221 std::size_t mud = 0u;
222 while (!reader.atEnd()) {
223 switch (reader.readNext()) {
224
225 case QXmlStreamReader::Invalid:
226 readError(reader);
227
228 case QXmlStreamReader::StartElement:
229 if (mud) {
230 ++mud;
231 PASS;
232 } else if (reader.qualifiedName() == path) {
233 PASS;
234 recurse(reader, writer, paths...);
235 } else {
236 ++mud;
237 PASS;
238 }
239 break;
240 case QXmlStreamReader::EndElement:
241 if (mud) {
242 --mud;
243 PASS;
244 } else {
245 PASS;
246 return;
247 }
248 break;
249 default:
250 PASS;
251 break;
252 };
253 }
254 }
255
main(int argc,char * argv[])256 int main(int argc, char * argv[]) {
257 QCoreApplication app(argc, argv);
258
259 if (argc != 3) {
260 std::cerr << "Usage: " << argv[0] << " <input file> <output file>"
261 << std::endl;
262 return 1;
263 }
264
265 inFilename = argv[1];
266 QFile inFile(inFilename);
267 if (!inFile.open(QIODevice::ReadOnly)) {
268 std::cerr << "Failed to open input file: " << inFilename << std::endl;
269 return 2;
270 }
271 inFilePath = QFileInfo(inFilename).canonicalPath();
272
273 QFile outFile(argv[2]);
274 if (!outFile.open(QIODevice::WriteOnly)) {
275 std::cerr << "Failed to open output file: " << argv[2] << std::endl;
276 return 3;
277 }
278
279
280 QXmlStreamReader reader(&inFile);
281 QXmlStreamWriter writer(&outFile);
282 writer.setAutoFormatting(true);
283 recurse(reader, writer, "TS", "context", "message");
284 }
285