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