1 /* This file is part of qjson
2   *
3   * Copyright (C) 2009 Till Adam <adam@kde.org>
4   * Copyright (C) 2009 Flavio Castelli <flavio@castelli.name>
5   * Copyright (C) 2016 Anton Kudryavtsev <a.kudryavtsev@netris.ru>
6   *
7   * This library is free software; you can redistribute it and/or
8   * modify it under the terms of the GNU Lesser General Public
9   * License version 2.1, as published by the Free Software Foundation.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with this library; see the file COPYING.LIB.  If not, write to
18   * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19   * Boston, MA 02110-1301, USA.
20   */
21 
22 #include "serializer.h"
23 
24 #include <QtCore/QDataStream>
25 #include <QtCore/QStringList>
26 #include <QtCore/QVariant>
27 
28 // cmath does #undef for isnan and isinf macroses what can be defined in math.h
29 #if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
30 # include <math.h>
31 #else
32 # include <cmath>
33 #endif
34 
35 #ifdef Q_OS_SOLARIS
36 # ifndef isinf
37 #  include <ieeefp.h>
38 #  define isinf(x) (!finite((x)) && (x)==(x))
39 # endif
40 #endif
41 
42 #ifdef _MSC_VER  // using MSVC compiler
43 #include <float.h>
44 #endif
45 
46 using namespace QJson;
47 
48 class Serializer::SerializerPrivate {
49   public:
SerializerPrivate()50     SerializerPrivate() :
51       specialNumbersAllowed(false),
52       indentMode(QJson::IndentNone),
53       doublePrecision(6) {
54         errorMessage.clear();
55     }
56     QString errorMessage;
57     bool specialNumbersAllowed;
58     IndentMode indentMode;
59     int doublePrecision;
60 
61     QByteArray serialize( const QVariant &v, bool *ok, int indentLevel = 0);
62 
63     static QByteArray buildIndent(int spaces);
64     static QByteArray escapeString( const QString& str );
65     static QByteArray join( const QList<QByteArray>& list, const QByteArray& sep );
66     static QByteArray join( const QList<QByteArray>& list, char sep );
67 };
68 
join(const QList<QByteArray> & list,const QByteArray & sep)69 QByteArray Serializer::SerializerPrivate::join( const QList<QByteArray>& list, const QByteArray& sep ) {
70   QByteArray res;
71   Q_FOREACH( const QByteArray& i, list ) {
72     if ( !res.isEmpty() )
73       res += sep;
74     res += i;
75   }
76   return res;
77 }
78 
join(const QList<QByteArray> & list,char sep)79 QByteArray Serializer::SerializerPrivate::join( const QList<QByteArray>& list, char sep ) {
80   QByteArray res;
81   Q_FOREACH( const QByteArray& i, list ) {
82     if ( !res.isEmpty() )
83       res += sep;
84     res += i;
85   }
86   return res;
87 }
88 
serialize(const QVariant & v,bool * ok,int indentLevel)89 QByteArray Serializer::SerializerPrivate::serialize( const QVariant &v, bool *ok, int indentLevel)
90 {
91   QByteArray str;
92   const QVariant::Type type = v.type();
93 
94   if ( ! v.isValid() ) { // invalid or null?
95     str = "null";
96   } else if (( type == QVariant::List ) || ( type == QVariant::StringList )) { // an array or a stringlist?
97     const QVariantList list = v.toList();
98     QList<QByteArray> values;
99     Q_FOREACH( const QVariant& var, list )
100     {
101       QByteArray serializedValue;
102 
103       serializedValue = serialize( var, ok, indentLevel+1);
104 
105       if ( !*ok ) {
106         break;
107       }
108       switch(indentMode) {
109         case QJson::IndentFull :
110         case QJson::IndentMedium :
111         case QJson::IndentMinimum :
112           values << serializedValue;
113           break;
114         case QJson::IndentCompact :
115         case QJson::IndentNone :
116         default:
117           values << serializedValue.trimmed();
118           break;
119       }
120     }
121 
122     if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull ) {
123       QByteArray indent = buildIndent(indentLevel);
124       str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']';
125     }
126     else if (indentMode == QJson::IndentMinimum) {
127       QByteArray indent = buildIndent(indentLevel);
128       str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']';
129     }
130     else if (indentMode == QJson::IndentCompact) {
131       str = '[' + join( values, "," ) + ']';
132     }
133     else {
134       str = "[ " + join( values, ", " ) + " ]";
135     }
136 
137   } else if ( type == QVariant::Map ) { // variant is a map?
138     const QVariantMap vmap = v.toMap();
139 
140     if (indentMode == QJson::IndentMinimum) {
141       QByteArray indent = buildIndent(indentLevel);
142       str = indent + "{ ";
143     }
144     else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
145       QByteArray indent = buildIndent(indentLevel);
146       QByteArray nextindent = buildIndent(indentLevel + 1);
147       str = indent + "{\n" + nextindent;
148     }
149     else if (indentMode == QJson::IndentCompact) {
150       str = "{";
151     }
152     else {
153       str = "{ ";
154     }
155 
156     QList<QByteArray> pairs;
157     for (QVariantMap::const_iterator it = vmap.begin(), end = vmap.end(); it != end; ++it) {
158       indentLevel++;
159       QByteArray serializedValue = serialize( it.value(), ok, indentLevel);
160       indentLevel--;
161       if ( !*ok ) {
162         break;
163       }
164       QByteArray key   = escapeString( it.key() );
165       QByteArray value = serializedValue.trimmed();
166       if (indentMode == QJson::IndentCompact) {
167         pairs << key + ':' + value;
168       } else {
169         pairs << key + " : " + value;
170       }
171     }
172 
173     if (indentMode == QJson::IndentFull) {
174       QByteArray indent = buildIndent(indentLevel + 1);
175       str += join( pairs, ",\n" + indent);
176     }
177     else if (indentMode == QJson::IndentCompact) {
178       str += join( pairs, ',' );
179     }
180     else {
181       str += join( pairs, ", " );
182     }
183 
184     if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
185       QByteArray indent = buildIndent(indentLevel);
186       str += '\n' + indent + '}';
187     }
188     else if (indentMode == QJson::IndentCompact) {
189       str += '}';
190     }
191     else {
192       str += " }";
193     }
194 
195   } else if ( type == QVariant::Hash ) { // variant is a hash?
196     const QVariantHash vhash = v.toHash();
197 
198     if (indentMode == QJson::IndentMinimum) {
199       QByteArray indent = buildIndent(indentLevel);
200       str = indent + "{ ";
201     }
202     else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
203       QByteArray indent = buildIndent(indentLevel);
204       QByteArray nextindent = buildIndent(indentLevel + 1);
205       str = indent + "{\n" + nextindent;
206     }
207     else if (indentMode == QJson::IndentCompact) {
208       str = "{";
209     }
210     else {
211       str = "{ ";
212     }
213 
214     QList<QByteArray> pairs;
215     for (QVariantHash::const_iterator it = vhash.begin(), end = vhash.end(); it != end; ++it) {
216       QByteArray serializedValue = serialize( it.value(), ok, indentLevel + 1);
217 
218       if ( !*ok ) {
219         break;
220       }
221       QByteArray key   = escapeString( it.key() );
222       QByteArray value = serializedValue.trimmed();
223       if (indentMode == QJson::IndentCompact) {
224         pairs << key + ':' + value;
225       } else {
226         pairs << key + " : " + value;
227       }
228     }
229 
230     if (indentMode == QJson::IndentFull) {
231       QByteArray indent = buildIndent(indentLevel + 1);
232       str += join( pairs, ",\n" + indent);
233     }
234     else if (indentMode == QJson::IndentCompact) {
235       str += join( pairs, ',' );
236     }
237     else {
238       str += join( pairs, ", " );
239     }
240 
241     if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
242       QByteArray indent = buildIndent(indentLevel);
243       str += '\n' + indent + '}';
244     }
245     else if (indentMode == QJson::IndentCompact) {
246       str += '}';
247     }
248     else {
249       str += " }";
250     }
251 
252   } else {
253     // Add indent, we may need to remove it later for some layouts
254     switch(indentMode) {
255       case QJson::IndentFull :
256       case QJson::IndentMedium :
257       case QJson::IndentMinimum :
258         str += buildIndent(indentLevel);
259         break;
260       case QJson::IndentCompact :
261       case QJson::IndentNone :
262       default:
263         break;
264     }
265 
266     if (( type == QVariant::String ) ||  ( type == QVariant::ByteArray )) { // a string or a byte array?
267       str += escapeString( v.toString() );
268     } else if (( type == QVariant::Double) || ((QMetaType::Type)type == QMetaType::Float)) { // a double or a float?
269       const double value = v.toDouble();
270   #if defined _WIN32 && !defined(Q_OS_SYMBIAN)
271       const bool special = _isnan(value) || !_finite(value);
272   #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
273       const bool special = isnan(value) || isinf(value);
274   #else
275       const bool special = std::isnan(value) || std::isinf(value);
276   #endif
277       if (special) {
278         if (specialNumbersAllowed) {
279   #if defined _WIN32 && !defined(Q_OS_SYMBIAN)
280           if (_isnan(value)) {
281   #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
282           if (isnan(value)) {
283   #else
284           if (std::isnan(value)) {
285   #endif
286             str += "NaN";
287           } else {
288             if (value<0) {
289               str += '-';
290             }
291             str += "Infinity";
292           }
293         } else {
294           errorMessage += QLatin1String("Attempt to write NaN or infinity, which is not supported by json\n");
295           *ok = false;
296       }
297       } else {
298         str = QByteArray::number( value , 'g', doublePrecision);
299         if( !str.contains( '.' ) && !str.contains( 'e' ) ) {
300           str += ".0";
301         }
302       }
303     } else if ( type == QVariant::Bool ) { // boolean value?
304       str += ( v.toBool() ? "true" : "false" );
305     } else if ( type == QVariant::ULongLong ) { // large unsigned number?
306       str += QByteArray::number( v.value<qulonglong>() );
307     } else if ( type == QVariant::UInt ) { // unsigned int number?
308       str += QByteArray::number( v.value<quint32>() );
309     } else if ( v.canConvert<qlonglong>() ) { // any signed number?
310       str += QByteArray::number( v.value<qlonglong>() );
311     } else if ( v.canConvert<int>() ) { // unsigned short number?
312       str += QByteArray::number( v.value<int>() );
313     } else if ( v.canConvert<QString>() ){ // can value be converted to string?
314       // this will catch QDate, QDateTime, QUrl, ...
315       str += escapeString( v.toString() );
316       //TODO: catch other values like QImage, QRect, ...
317     } else {
318       *ok = false;
319       errorMessage += QLatin1String("Cannot serialize ");
320       errorMessage += v.toString();
321       errorMessage += QLatin1String(" because type ");
322       errorMessage += QLatin1String(v.typeName());
323       errorMessage += QLatin1String(" is not supported by QJson\n");
324     }
325   }
326   if ( *ok )
327   {
328     return str;
329   }
330   else
331     return QByteArray();
332 }
333 
334 QByteArray Serializer::SerializerPrivate::buildIndent(int spaces)
335 {
336    QByteArray indent;
337    if (spaces < 0) {
338      spaces = 0;
339    }
340    for (int i = 0; i < spaces; i++ ) {
341      indent += ' ';
342    }
343    return indent;
344 }
345 
346 QByteArray Serializer::SerializerPrivate::escapeString( const QString& str )
347 {
348   QByteArray result;
349   result.reserve(str.size() + 2);
350   result.append('\"');
351   for (QString::const_iterator it = str.begin(), end = str.end(); it != end; ++it) {
352     ushort unicode = it->unicode();
353     switch ( unicode ) {
354       case '\"':
355         result.append("\\\"");
356         break;
357       case '\\':
358         result.append("\\\\");
359         break;
360       case '\b':
361         result.append("\\b");
362         break;
363       case '\f':
364         result.append("\\f");
365         break;
366       case '\n':
367         result.append("\\n");
368         break;
369       case '\r':
370         result.append("\\r");
371         break;
372       case '\t':
373         result.append("\\t");
374         break;
375       default:
376         if ( unicode > 0x1F && unicode < 128 ) {
377           result.append(static_cast<char>(unicode));
378         } else {
379           char escaped[7];
380           qsnprintf(escaped, sizeof(escaped)/sizeof(char), "\\u%04x", unicode);
381           result.append(escaped);
382         }
383     }
384   }
385   result.append('\"');
386   return result;
387 }
388 
389 Serializer::Serializer()
390   : d( new SerializerPrivate )
391 {
392 }
393 
394 Serializer::~Serializer() {
395   delete d;
396 }
397 
398 void Serializer::serialize( const QVariant& v, QIODevice* io, bool* ok)
399 {
400   Q_ASSERT( io );
401   *ok = true;
402 
403   if (!io->isOpen()) {
404     if (!io->open(QIODevice::WriteOnly)) {
405       d->errorMessage = QLatin1String("Error opening device");
406       *ok = false;
407       return;
408     }
409   }
410 
411   if (!io->isWritable()) {
412     d->errorMessage = QLatin1String("Device is not readable");
413     io->close();
414     *ok = false;
415     return;
416   }
417 
418   const QByteArray str = serialize( v, ok);
419   if (*ok && (io->write(str) != str.count())) {
420     *ok = false;
421     d->errorMessage = QLatin1String("Something went wrong while writing to IO device");
422   }
423 }
424 
425 QByteArray Serializer::serialize( const QVariant &v)
426 {
427   bool ok;
428 
429   return serialize(v, &ok);
430 }
431 
432 QByteArray Serializer::serialize( const QVariant &v, bool *ok)
433 {
434   bool _ok = true;
435   d->errorMessage.clear();
436 
437   if (ok) {
438     *ok = true;
439   } else {
440     ok = &_ok;
441   }
442 
443   return d->serialize(v, ok);
444 }
445 
446 void QJson::Serializer::allowSpecialNumbers(bool allow) {
447   d->specialNumbersAllowed = allow;
448 }
449 
450 bool QJson::Serializer::specialNumbersAllowed() const {
451   return d->specialNumbersAllowed;
452 }
453 
454 void QJson::Serializer::setIndentMode(IndentMode mode) {
455   d->indentMode = mode;
456 }
457 
458 void QJson::Serializer::setDoublePrecision(int precision) {
459   d->doublePrecision = precision;
460 }
461 
462 IndentMode QJson::Serializer::indentMode() const {
463   return d->indentMode;
464 }
465 
466 QString QJson::Serializer::errorMessage() const {
467   return d->errorMessage;
468 }
469 
470