File indexing completed on 2025-01-05 04:01:15
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "cbor_write_json.hpp" 0008 #include <QCborValue> 0009 #include <QCborMap> 0010 #include <QCborArray> 0011 #include <cstdint> 0012 0013 /****************************************************************************** 0014 * These function are mostly taken from Qt code 0015 * See 0016 * https://github.com/qt/qtbase/blob/dev/src/corelib/serialization/qjsonwriter.cpp 0017 * https://github.com/qt/qtbase/blob/dev/src/corelib/text/qstringconverter_p.h 0018 ******************************************************************************/ 0019 0020 #ifndef __cpp_char8_t 0021 enum char8_t : uchar {}; 0022 #endif 0023 0024 0025 struct QUtf8BaseTraits 0026 { 0027 static const bool isTrusted = false; 0028 static const bool allowNonCharacters = true; 0029 static const bool skipAsciiHandling = false; 0030 static const int Error = -1; 0031 static const int EndOfString = -2; 0032 0033 static bool isValidCharacter(uint u) 0034 { return int(u) >= 0; } 0035 0036 static void appendByte(uchar *&ptr, uchar b) 0037 { *ptr++ = b; } 0038 0039 static void appendByte(char8_t *&ptr, char8_t b) 0040 { *ptr++ = b; } 0041 0042 static uchar peekByte(const uchar *ptr, qsizetype n = 0) 0043 { return ptr[n]; } 0044 0045 static uchar peekByte(const char8_t *ptr, int n = 0) 0046 { return ptr[n]; } 0047 0048 static qptrdiff availableBytes(const uchar *ptr, const uchar *end) 0049 { return end - ptr; } 0050 0051 static qptrdiff availableBytes(const char8_t *ptr, const char8_t *end) 0052 { return end - ptr; } 0053 0054 static void advanceByte(const uchar *&ptr, qsizetype n = 1) 0055 { ptr += n; } 0056 0057 static void advanceByte(const char8_t *&ptr, int n = 1) 0058 { ptr += n; } 0059 0060 static void appendUtf16(ushort *&ptr, ushort uc) 0061 { *ptr++ = uc; } 0062 0063 static void appendUtf16(char16_t *&ptr, ushort uc) 0064 { *ptr++ = char16_t(uc); } 0065 0066 static void appendUcs4(ushort *&ptr, uint uc) 0067 { 0068 appendUtf16(ptr, QChar::highSurrogate(uc)); 0069 appendUtf16(ptr, QChar::lowSurrogate(uc)); 0070 } 0071 0072 static void appendUcs4(char16_t *&ptr, char32_t uc) 0073 { 0074 appendUtf16(ptr, QChar::highSurrogate(uc)); 0075 appendUtf16(ptr, QChar::lowSurrogate(uc)); 0076 } 0077 0078 static ushort peekUtf16(const ushort *ptr, qsizetype n = 0) 0079 { return ptr[n]; } 0080 0081 static ushort peekUtf16(const char16_t *ptr, int n = 0) 0082 { return ptr[n]; } 0083 0084 static qptrdiff availableUtf16(const ushort *ptr, const ushort *end) 0085 { return end - ptr; } 0086 0087 static qptrdiff availableUtf16(const char16_t *ptr, const char16_t *end) 0088 { return end - ptr; } 0089 0090 static void advanceUtf16(const ushort *&ptr, qsizetype n = 1) 0091 { ptr += n; } 0092 0093 static void advanceUtf16(const char16_t *&ptr, int n = 1) 0094 { ptr += n; } 0095 0096 // it's possible to output to UCS-4 too 0097 static void appendUtf16(uint *&ptr, ushort uc) 0098 { *ptr++ = uc; } 0099 0100 static void appendUtf16(char32_t *&ptr, ushort uc) 0101 { *ptr++ = char32_t(uc); } 0102 0103 static void appendUcs4(uint *&ptr, uint uc) 0104 { *ptr++ = uc; } 0105 0106 static void appendUcs4(char32_t *&ptr, uint uc) 0107 { *ptr++ = char32_t(uc); } 0108 }; 0109 0110 0111 namespace QUtf8Functions 0112 { 0113 /// returns 0 on success; errors can only happen if \a u is a surrogate: 0114 /// Error if \a u is a low surrogate; 0115 /// if \a u is a high surrogate, Error if the next isn't a low one, 0116 /// EndOfString if we run into the end of the string. 0117 template <typename Traits, typename OutputPtr, typename InputPtr> inline 0118 int toUtf8(ushort u, OutputPtr &dst, InputPtr &src, InputPtr end) 0119 { 0120 if (!Traits::skipAsciiHandling && u < 0x80) { 0121 // U+0000 to U+007F (US-ASCII) - one byte 0122 Traits::appendByte(dst, uchar(u)); 0123 return 0; 0124 } else if (u < 0x0800) { 0125 // U+0080 to U+07FF - two bytes 0126 // first of two bytes 0127 Traits::appendByte(dst, 0xc0 | uchar(u >> 6)); 0128 } else { 0129 if (!QChar::isSurrogate(u)) { 0130 // U+0800 to U+FFFF (except U+D800-U+DFFF) - three bytes 0131 if (!Traits::allowNonCharacters && QChar::isNonCharacter(u)) 0132 return Traits::Error; 0133 0134 // first of three bytes 0135 Traits::appendByte(dst, 0xe0 | uchar(u >> 12)); 0136 } else { 0137 // U+10000 to U+10FFFF - four bytes 0138 // need to get one extra codepoint 0139 if (Traits::availableUtf16(src, end) == 0) 0140 return Traits::EndOfString; 0141 0142 ushort low = Traits::peekUtf16(src); 0143 if (!QChar::isHighSurrogate(u)) 0144 return Traits::Error; 0145 if (!QChar::isLowSurrogate(low)) 0146 return Traits::Error; 0147 0148 Traits::advanceUtf16(src); 0149 uint ucs4 = QChar::surrogateToUcs4(u, low); 0150 0151 if (!Traits::allowNonCharacters && QChar::isNonCharacter(ucs4)) 0152 return Traits::Error; 0153 0154 // first byte 0155 Traits::appendByte(dst, 0xf0 | (uchar(ucs4 >> 18) & 0xf)); 0156 0157 // second of four bytes 0158 Traits::appendByte(dst, 0x80 | (uchar(ucs4 >> 12) & 0x3f)); 0159 0160 // for the rest of the bytes 0161 u = ushort(ucs4); 0162 } 0163 0164 // second to last byte 0165 Traits::appendByte(dst, 0x80 | (uchar(u >> 6) & 0x3f)); 0166 } 0167 0168 // last byte 0169 Traits::appendByte(dst, 0x80 | (u & 0x3f)); 0170 return 0; 0171 } 0172 } 0173 0174 0175 static void objectContentToJson(const QCborMap& p, QByteArray &json, int indent, bool compact); 0176 static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact); 0177 0178 static inline uchar hexdig(uint u) 0179 { 0180 return (u < 0xa ? '0' + u : 'a' + u - 0xa); 0181 } 0182 0183 static QByteArray escapedString(const QString &s) 0184 { 0185 // give it a minimum size to ensure the resize() below always adds enough space 0186 QByteArray ba(qMax(s.length(), 16), Qt::Uninitialized); 0187 0188 uchar *cursor = reinterpret_cast<uchar *>(const_cast<char *>(ba.constData())); 0189 const uchar *ba_end = cursor + ba.length(); 0190 const ushort *src = reinterpret_cast<const ushort *>(s.constBegin()); 0191 const ushort *const end = reinterpret_cast<const ushort *>(s.constEnd()); 0192 0193 while (src != end) { 0194 if (cursor >= ba_end - 6) { 0195 // ensure we have enough space 0196 int pos = cursor - (const uchar *)ba.constData(); 0197 ba.resize(ba.size()*2); 0198 cursor = (uchar *)ba.data() + pos; 0199 ba_end = (const uchar *)ba.constData() + ba.length(); 0200 } 0201 0202 uint u = *src++; 0203 if (u < 0x80) { 0204 if (u < 0x20 || u == 0x22 || u == 0x5c) { 0205 *cursor++ = '\\'; 0206 switch (u) { 0207 case 0x22: 0208 *cursor++ = '"'; 0209 break; 0210 case 0x5c: 0211 *cursor++ = '\\'; 0212 break; 0213 case 0x8: 0214 *cursor++ = 'b'; 0215 break; 0216 case 0xc: 0217 *cursor++ = 'f'; 0218 break; 0219 case 0xa: 0220 *cursor++ = 'n'; 0221 break; 0222 case 0xd: 0223 *cursor++ = 'r'; 0224 break; 0225 case 0x9: 0226 *cursor++ = 't'; 0227 break; 0228 default: 0229 *cursor++ = 'u'; 0230 *cursor++ = '0'; 0231 *cursor++ = '0'; 0232 *cursor++ = hexdig(u>>4); 0233 *cursor++ = hexdig(u & 0xf); 0234 } 0235 } else { 0236 *cursor++ = (uchar)u; 0237 } 0238 } else if (QUtf8Functions::toUtf8<QUtf8BaseTraits>(u, cursor, src, end) < 0) { 0239 // failed to get valid utf8 use JSON escape sequence 0240 *cursor++ = '\\'; 0241 *cursor++ = 'u'; 0242 *cursor++ = hexdig(u>>12 & 0x0f); 0243 *cursor++ = hexdig(u>>8 & 0x0f); 0244 *cursor++ = hexdig(u>>4 & 0x0f); 0245 *cursor++ = hexdig(u & 0x0f); 0246 } 0247 } 0248 0249 ba.resize(cursor - (const uchar *)ba.constData()); 0250 return ba; 0251 } 0252 0253 static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool compact) 0254 { 0255 QCborValue::Type type = v.type(); 0256 switch (type) { 0257 case QCborValue::True: 0258 json += "true"; 0259 break; 0260 case QCborValue::False: 0261 json += "false"; 0262 break; 0263 case QCborValue::Integer: 0264 json += QByteArray::number(v.toInteger()); 0265 break; 0266 case QCborValue::Double: { 0267 const double d = v.toDouble(); 0268 if (qIsFinite(d)) 0269 { 0270 QByteArray dstr; 0271 if ( compact ) 0272 { 0273 // prec is weird with 'g' so we emulate it 0274 QByteArray f = QByteArray::number(d, 'f', 3); 0275 QByteArray e = QByteArray::number(d, 'e', 3); 0276 dstr = e.size() < f.size() ? e : f; 0277 } 0278 else 0279 { 0280 dstr = QByteArray::number(d, 'g', QLocale::FloatingPointShortest); 0281 } 0282 if ( dstr.endsWith(".000") ) 0283 dstr = dstr.left(dstr.size()-4); 0284 json += dstr; 0285 } 0286 else 0287 { 0288 json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4) 0289 } 0290 break; 0291 } 0292 case QCborValue::String: 0293 json += '"'; 0294 json += escapedString(v.toString()); 0295 json += '"'; 0296 break; 0297 case QCborValue::Array: 0298 json += compact ? "[" : "[\n"; 0299 arrayContentToJson(v.toArray(), json, indent + (compact ? 0 : 1), compact); 0300 json += QByteArray(4*indent, ' '); 0301 json += ']'; 0302 break; 0303 case QCborValue::Map: 0304 json += compact ? "{" : "{\n"; 0305 objectContentToJson(v.toMap(), json, indent + (compact ? 0 : 1), compact); 0306 json += QByteArray(4*indent, ' '); 0307 json += '}'; 0308 break; 0309 case QCborValue::Null: 0310 default: 0311 json += "null"; 0312 } 0313 } 0314 0315 static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact) 0316 { 0317 if ( a.empty() ) 0318 return; 0319 0320 QByteArray indentString(4*indent, ' '); 0321 0322 qsizetype i = 0; 0323 while (true) { 0324 json += indentString; 0325 valueToJson(a.at(i), json, indent, compact); 0326 0327 if (++i == a.size()) { 0328 if (!compact) 0329 json += '\n'; 0330 break; 0331 } 0332 0333 json += compact ? "," : ",\n"; 0334 } 0335 } 0336 0337 0338 static void objectContentToJson(const QCborMap& o, QByteArray &json, int indent, bool compact) 0339 { 0340 if ( o.empty() ) 0341 return; 0342 0343 QByteArray indentString(4*indent, ' '); 0344 0345 auto it = o.begin(); 0346 auto end = o.end(); 0347 0348 while (true) { 0349 json += indentString; 0350 json += '"'; 0351 json += escapedString(it.key().toString()); 0352 json += compact ? "\":" : "\": "; 0353 valueToJson(it.value(), json, indent, compact); 0354 0355 ++it; 0356 if ( it == end ) { 0357 if (!compact) 0358 json += '\n'; 0359 break; 0360 } 0361 0362 json += compact ? "," : ",\n"; 0363 } 0364 } 0365 0366 QByteArray glaxnimate::io::lottie::cbor_write_json(const QCborMap &o, bool compact) 0367 { 0368 QByteArray json; 0369 json += compact ? "{" : "{\n"; 0370 objectContentToJson(o, json, 0, compact); 0371 json += compact ? "}" : "}\n"; 0372 return json; 0373 }