1 /*
2 * JSON Tiled Plugin
3 * Copyright 2011, Porfírio José Pereira Ribeiro <porfirioribeiro@gmail.com>
4 * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
5 *
6 * This file is part of Tiled.
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU General Public License along with
19 * this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "jsonplugin.h"
23
24 #include "maptovariantconverter.h"
25 #include "varianttomapconverter.h"
26 #include "savefile.h"
27
28 #include "qjsonparser/json.h"
29
30 #include <QCoreApplication>
31 #include <QFile>
32 #include <QFileInfo>
33 #include <QJsonDocument>
34 #include <QJsonObject>
35 #include <QTextStream>
36
37 namespace Json {
38
initialize()39 void JsonPlugin::initialize()
40 {
41 addObject(new JsonMapFormat(JsonMapFormat::Json, this));
42 addObject(new JsonMapFormat(JsonMapFormat::JavaScript, this));
43 addObject(new JsonTilesetFormat(this));
44 addObject(new JsonObjectTemplateFormat(this));
45 }
46
47
JsonMapFormat(SubFormat subFormat,QObject * parent)48 JsonMapFormat::JsonMapFormat(SubFormat subFormat, QObject *parent)
49 : Tiled::MapFormat(parent)
50 , mSubFormat(subFormat)
51 {}
52
read(const QString & fileName)53 std::unique_ptr<Tiled::Map> JsonMapFormat::read(const QString &fileName)
54 {
55 QFile file(fileName);
56 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
57 mError = QCoreApplication::translate("File Errors", "Could not open file for reading.");
58 return nullptr;
59 }
60
61 QByteArray contents = file.readAll();
62 if (mSubFormat == JavaScript && contents.size() > 0 && contents[0] != '{') {
63 // Scan past JSONP prefix; look for an open curly at the start of the line
64 int i = contents.indexOf("\n{");
65 if (i > 0) {
66 contents.remove(0, i);
67 contents = contents.trimmed(); // potential trailing whitespace
68 if (contents.endsWith(';')) contents.chop(1);
69 if (contents.endsWith(')')) contents.chop(1);
70 }
71 }
72
73 QJsonParseError error;
74 const QJsonDocument document = QJsonDocument::fromJson(contents, &error);
75
76 if (error.error != QJsonParseError::NoError) {
77 mError = tr("Error parsing file: %1").arg(error.errorString());
78 return nullptr;
79 }
80
81 Tiled::VariantToMapConverter converter;
82 auto map = converter.toMap(document.toVariant(), QFileInfo(fileName).dir());
83
84 if (!map)
85 mError = converter.errorString();
86
87 return map;
88 }
89
write(const Tiled::Map * map,const QString & fileName,Options options)90 bool JsonMapFormat::write(const Tiled::Map *map,
91 const QString &fileName,
92 Options options)
93 {
94 Tiled::SaveFile file(fileName);
95
96 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
97 mError = QCoreApplication::translate("File Errors", "Could not open file for writing.");
98 return false;
99 }
100
101 Tiled::MapToVariantConverter converter{1};
102 QVariant variant = converter.toVariant(*map, QFileInfo(fileName).dir());
103
104 JsonWriter writer;
105 writer.setAutoFormatting(!options.testFlag(WriteMinimized));
106
107 if (!writer.stringify(variant)) {
108 // This can only happen due to coding error
109 mError = writer.errorString();
110 return false;
111 }
112
113 QTextStream out(file.device());
114 if (mSubFormat == JavaScript) {
115 // Trim and escape name
116 JsonWriter nameWriter;
117 QString baseName = QFileInfo(fileName).baseName();
118 nameWriter.stringify(baseName);
119 out << "(function(name,data){\n if(typeof onTileMapLoaded === 'undefined') {\n";
120 out << " if(typeof TileMaps === 'undefined') TileMaps = {};\n";
121 out << " TileMaps[name] = data;\n";
122 out << " } else {\n";
123 out << " onTileMapLoaded(name,data);\n";
124 out << " }\n";
125 out << " if(typeof module === 'object' && module && module.exports) {\n";
126 out << " module.exports = data;\n";
127 out << " }})(" << nameWriter.result() << ",\n";
128 }
129 out << writer.result();
130 if (mSubFormat == JavaScript) {
131 out << ");";
132 }
133
134 if (file.error() != QFileDevice::NoError) {
135 mError = tr("Error while writing file:\n%1").arg(file.errorString());
136 return false;
137 }
138
139 if (!file.commit()) {
140 mError = file.errorString();
141 return false;
142 }
143
144 return true;
145 }
146
nameFilter() const147 QString JsonMapFormat::nameFilter() const
148 {
149 if (mSubFormat == Json)
150 return tr("JSON map files [Tiled 1.1] (*.json)");
151 else
152 return tr("JavaScript map files [Tiled 1.1] (*.js)");
153 }
154
shortName() const155 QString JsonMapFormat::shortName() const
156 {
157 if (mSubFormat == Json)
158 return QStringLiteral("json1");
159 else
160 return QStringLiteral("js1");
161 }
162
supportsFile(const QString & fileName) const163 bool JsonMapFormat::supportsFile(const QString &fileName) const
164 {
165 if (mSubFormat == Json) {
166 if (!fileName.endsWith(QLatin1String(".json"), Qt::CaseInsensitive))
167 return false;
168 } else {
169 if (!fileName.endsWith(QLatin1String(".js"), Qt::CaseInsensitive))
170 return false;
171 }
172
173 QFile file(fileName);
174 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
175 QByteArray contents = file.readAll();
176
177 if (mSubFormat == JavaScript && contents.size() > 0 && contents[0] != '{') {
178 // Scan past JSONP prefix; look for an open curly at the start of the line
179 int i = contents.indexOf("\n{");
180 if (i > 0) {
181 contents.remove(0, i);
182 contents = contents.trimmed(); // potential trailing whitespace
183 if (contents.endsWith(';')) contents.chop(1);
184 if (contents.endsWith(')')) contents.chop(1);
185 }
186 }
187
188 const QJsonObject object = QJsonDocument::fromJson(contents).object();
189
190 // This is a good indication, but not present in older map files
191 if (object.value(QLatin1String("type")).toString() == QLatin1String("map"))
192 return true;
193
194 // Guess based on expected property
195 if (object.contains(QLatin1String("orientation")))
196 return true;
197 }
198
199 return false;
200 }
201
errorString() const202 QString JsonMapFormat::errorString() const
203 {
204 return mError;
205 }
206
207
JsonTilesetFormat(QObject * parent)208 JsonTilesetFormat::JsonTilesetFormat(QObject *parent)
209 : Tiled::TilesetFormat(parent)
210 {
211 }
212
read(const QString & fileName)213 Tiled::SharedTileset JsonTilesetFormat::read(const QString &fileName)
214 {
215 QFile file(fileName);
216
217 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
218 mError = QCoreApplication::translate("File Errors", "Could not open file for reading.");
219 return Tiled::SharedTileset();
220 }
221
222 QJsonParseError error;
223 const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &error);
224
225 if (error.error != QJsonParseError::NoError) {
226 mError = tr("Error parsing file: %1").arg(error.errorString());
227 return Tiled::SharedTileset();
228 }
229
230 Tiled::VariantToMapConverter converter;
231 Tiled::SharedTileset tileset = converter.toTileset(document.toVariant(),
232 QFileInfo(fileName).dir());
233
234 if (!tileset)
235 mError = converter.errorString();
236
237 return tileset;
238 }
239
supportsFile(const QString & fileName) const240 bool JsonTilesetFormat::supportsFile(const QString &fileName) const
241 {
242 if (fileName.endsWith(QLatin1String(".json"), Qt::CaseInsensitive)) {
243 QFile file(fileName);
244
245 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
246 const QJsonObject object = QJsonDocument::fromJson(file.readAll()).object();
247
248 // This is a good indication, but not present in older external tilesets
249 if (object.value(QLatin1String("type")).toString() == QLatin1String("tileset"))
250 return true;
251
252 // Guess based on some expected properties
253 if (object.contains(QLatin1String("name")) &&
254 object.contains(QLatin1String("tilewidth")) &&
255 object.contains(QLatin1String("tileheight")))
256 return true;
257 }
258 }
259
260 return false;
261 }
262
write(const Tiled::Tileset & tileset,const QString & fileName,Options options)263 bool JsonTilesetFormat::write(const Tiled::Tileset &tileset,
264 const QString &fileName,
265 Options options)
266 {
267 Tiled::SaveFile file(fileName);
268
269 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
270 mError = QCoreApplication::translate("File Errors", "Could not open file for writing.");
271 return false;
272 }
273
274 Tiled::MapToVariantConverter converter{1};
275 QVariant variant = converter.toVariant(tileset, QFileInfo(fileName).dir());
276
277 JsonWriter writer;
278 writer.setAutoFormatting(!options.testFlag(WriteMinimized));
279
280 if (!writer.stringify(variant)) {
281 // This can only happen due to coding error
282 mError = writer.errorString();
283 return false;
284 }
285
286 QTextStream out(file.device());
287 out << writer.result();
288
289 if (file.error() != QFileDevice::NoError) {
290 mError = tr("Error while writing file:\n%1").arg(file.errorString());
291 return false;
292 }
293
294 if (!file.commit()) {
295 mError = file.errorString();
296 return false;
297 }
298
299 return true;
300 }
301
nameFilter() const302 QString JsonTilesetFormat::nameFilter() const
303 {
304 return tr("JSON tileset files [Tiled 1.1] (*.json)");
305 }
306
shortName() const307 QString JsonTilesetFormat::shortName() const
308 {
309 return QStringLiteral("json1");
310 }
311
errorString() const312 QString JsonTilesetFormat::errorString() const
313 {
314 return mError;
315 }
316
JsonObjectTemplateFormat(QObject * parent)317 JsonObjectTemplateFormat::JsonObjectTemplateFormat(QObject *parent)
318 : Tiled::ObjectTemplateFormat(parent)
319 {
320 }
321
read(const QString & fileName)322 std::unique_ptr<Tiled::ObjectTemplate> JsonObjectTemplateFormat::read(const QString &fileName)
323 {
324 QFile file(fileName);
325
326 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
327 mError = QCoreApplication::translate("File Errors", "Could not open file for reading.");
328 return nullptr;
329 }
330
331 QJsonParseError error;
332 const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &error);
333
334 if (error.error != QJsonParseError::NoError) {
335 mError = tr("Error parsing file: %1").arg(error.errorString());
336 return nullptr;
337 }
338
339 Tiled::VariantToMapConverter converter;
340 auto objectTemplate = converter.toObjectTemplate(document.toVariant(),
341 QFileInfo(fileName).dir());
342
343 if (!objectTemplate)
344 mError = converter.errorString();
345 else
346 objectTemplate->setFileName(fileName);
347
348 return objectTemplate;
349 }
350
supportsFile(const QString & fileName) const351 bool JsonObjectTemplateFormat::supportsFile(const QString &fileName) const
352 {
353 if (fileName.endsWith(QLatin1String(".json"), Qt::CaseInsensitive)) {
354 QFile file(fileName);
355
356 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
357 const QJsonObject object = QJsonDocument::fromJson(file.readAll()).object();
358
359 if (object.value(QLatin1String("type")).toString() == QLatin1String("template"))
360 return true;
361 }
362 }
363
364 return false;
365 }
366
write(const Tiled::ObjectTemplate * objectTemplate,const QString & fileName)367 bool JsonObjectTemplateFormat::write(const Tiled::ObjectTemplate *objectTemplate, const QString &fileName)
368 {
369 Tiled::SaveFile file(fileName);
370
371 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
372 mError = QCoreApplication::translate("File Errors", "Could not open file for writing.");
373 return false;
374 }
375
376 Tiled::MapToVariantConverter converter{1};
377 QVariant variant = converter.toVariant(*objectTemplate, QFileInfo(fileName).dir());
378
379 JsonWriter writer;
380 writer.setAutoFormatting(true);
381
382 if (!writer.stringify(variant)) {
383 // This can only happen due to coding error
384 mError = writer.errorString();
385 return false;
386 }
387
388 QTextStream out(file.device());
389 out << writer.result();
390
391 if (file.error() != QFileDevice::NoError) {
392 mError = tr("Error while writing file:\n%1").arg(file.errorString());
393 return false;
394 }
395
396 if (!file.commit()) {
397 mError = file.errorString();
398 return false;
399 }
400
401 return true;
402 }
403
nameFilter() const404 QString JsonObjectTemplateFormat::nameFilter() const
405 {
406 return tr("JSON template files [Tiled 1.1] (*.json)");
407 }
408
shortName() const409 QString JsonObjectTemplateFormat::shortName() const
410 {
411 return QStringLiteral("json1");
412 }
413
errorString() const414 QString JsonObjectTemplateFormat::errorString() const
415 {
416 return mError;
417 }
418
419 } // namespace Json
420