Version 7.3.5.2, tag libreoffice-7.3.5.2
[LibreOffice.git] / tools / source / misc / json_writer.cxx
blobd6e34179f9307ac1777716d945da4a9f54940e12
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <tools/json_writer.hxx>
11 #include <stdio.h>
12 #include <algorithm>
13 #include <cstring>
14 #include <rtl/math.hxx>
16 namespace tools
18 /** These buffers are short-lived, so rather waste some space and avoid the cost of
19 * repeated calls into the allocator */
20 constexpr int DEFAULT_BUFFER_SIZE = 2048;
22 JsonWriter::JsonWriter()
23 : mpBuffer(static_cast<char*>(malloc(DEFAULT_BUFFER_SIZE)))
24 , mPos(mpBuffer)
25 , mSpaceAllocated(DEFAULT_BUFFER_SIZE)
26 , mStartNodeCount(0)
27 , mbFirstFieldInNode(true)
29 *mPos = '{';
30 ++mPos;
31 *mPos = ' ';
32 ++mPos;
35 JsonWriter::~JsonWriter()
37 assert(!mpBuffer && "forgot to extract data?");
38 free(mpBuffer);
41 ScopedJsonWriterNode JsonWriter::startNode(const char* pNodeName)
43 auto len = strlen(pNodeName);
44 ensureSpace(len + 6);
46 addCommaBeforeField();
48 *mPos = '"';
49 ++mPos;
50 memcpy(mPos, pNodeName, len);
51 mPos += len;
52 memcpy(mPos, "\": { ", 5);
53 mPos += 5;
54 mStartNodeCount++;
55 mbFirstFieldInNode = true;
56 return ScopedJsonWriterNode(*this);
59 void JsonWriter::endNode()
61 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
62 --mStartNodeCount;
63 ensureSpace(1);
64 *mPos = '}';
65 ++mPos;
66 mbFirstFieldInNode = false;
69 ScopedJsonWriterArray JsonWriter::startArray(const char* pNodeName)
71 auto len = strlen(pNodeName);
72 ensureSpace(len + 6);
74 addCommaBeforeField();
76 *mPos = '"';
77 ++mPos;
78 memcpy(mPos, pNodeName, len);
79 mPos += len;
80 memcpy(mPos, "\": [ ", 5);
81 mPos += 5;
82 mStartNodeCount++;
83 mbFirstFieldInNode = true;
84 return ScopedJsonWriterArray(*this);
87 void JsonWriter::endArray()
89 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
90 --mStartNodeCount;
91 ensureSpace(1);
92 *mPos = ']';
93 ++mPos;
94 mbFirstFieldInNode = false;
97 ScopedJsonWriterStruct JsonWriter::startStruct()
99 ensureSpace(6);
101 addCommaBeforeField();
103 *mPos = '{';
104 ++mPos;
105 *mPos = ' ';
106 ++mPos;
107 mStartNodeCount++;
108 mbFirstFieldInNode = true;
109 return ScopedJsonWriterStruct(*this);
112 void JsonWriter::endStruct()
114 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
115 --mStartNodeCount;
116 ensureSpace(1);
117 *mPos = '}';
118 ++mPos;
119 mbFirstFieldInNode = false;
122 static char getEscapementChar(char ch)
124 switch (ch)
126 case '\b':
127 return 'b';
128 case '\t':
129 return 't';
130 case '\n':
131 return 'n';
132 case '\f':
133 return 'f';
134 case '\r':
135 return 'r';
136 default:
137 return ch;
141 static bool writeEscapedSequence(sal_uInt32 ch, char*& pos)
143 switch (ch)
145 case '\b':
146 case '\t':
147 case '\n':
148 case '\f':
149 case '\r':
150 case '"':
151 case '/':
152 case '\\':
153 *pos++ = '\\';
154 *pos++ = getEscapementChar(ch);
155 return true;
156 // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript
157 // Write them in escaped '\u2028' or '\u2029' form
158 case 0x2028:
159 case 0x2029:
160 *pos++ = '\\';
161 *pos++ = 'u';
162 *pos++ = '2';
163 *pos++ = '0';
164 *pos++ = '2';
165 *pos++ = ch == 0x2028 ? '8' : '9';
166 return true;
167 default:
168 return false;
172 void JsonWriter::writeEscapedOUString(const OUString& rPropVal)
174 // Convert from UTF-16 to UTF-8 and perform escaping
175 sal_Int32 i = 0;
176 while (i < rPropVal.getLength())
178 sal_uInt32 ch = rPropVal.iterateCodePoints(&i);
179 if (writeEscapedSequence(ch, mPos))
180 continue;
181 if (ch <= 0x7F)
183 *mPos = static_cast<char>(ch);
184 ++mPos;
186 else if (ch <= 0x7FF)
188 *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */
189 ++mPos;
190 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
191 ++mPos;
193 else if (ch <= 0xFFFF)
195 *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */
196 ++mPos;
197 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
198 ++mPos;
199 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
200 ++mPos;
202 else
204 *mPos = 0xF0 | (ch >> 18); /* 11110xxx */
205 ++mPos;
206 *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */
207 ++mPos;
208 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
209 ++mPos;
210 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
211 ++mPos;
216 void JsonWriter::put(const char* pPropName, const OUString& rPropVal)
218 auto nPropNameLength = strlen(pPropName);
219 // But values can be any UTF-8,
220 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
221 auto nWorstCasePropValLength = rPropVal.getLength() * 6;
222 ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
224 addCommaBeforeField();
226 *mPos = '"';
227 ++mPos;
228 memcpy(mPos, pPropName, nPropNameLength);
229 mPos += nPropNameLength;
230 memcpy(mPos, "\": \"", 4);
231 mPos += 4;
233 writeEscapedOUString(rPropVal);
235 *mPos = '"';
236 ++mPos;
239 void JsonWriter::put(const char* pPropName, std::string_view rPropVal)
241 // we assume property names are ascii
242 auto nPropNameLength = strlen(pPropName);
243 // escaping can double the length
244 auto nWorstCasePropValLength = rPropVal.size() * 2;
245 ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
247 addCommaBeforeField();
249 *mPos = '"';
250 ++mPos;
251 memcpy(mPos, pPropName, nPropNameLength);
252 mPos += nPropNameLength;
253 memcpy(mPos, "\": \"", 4);
254 mPos += 4;
256 // copy and perform escaping
257 for (size_t i = 0; i < rPropVal.size(); ++i)
259 char ch = rPropVal[i];
260 switch (ch)
262 case '\b':
263 case '\t':
264 case '\n':
265 case '\f':
266 case '\r':
267 case '"':
268 case '/':
269 case '\\':
270 writeEscapedSequence(ch, mPos);
271 break;
272 case '\xE2': // Special processing of U+2028 and U+2029
273 if (i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80'
274 && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9'))
276 writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos);
277 i += 2;
278 break;
280 [[fallthrough]];
281 default:
282 *mPos = ch;
283 ++mPos;
284 break;
288 *mPos = '"';
289 ++mPos;
292 void JsonWriter::put(const char* pPropName, sal_Int64 nPropVal)
294 auto nPropNameLength = strlen(pPropName);
295 auto nWorstCasePropValLength = 32;
296 ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
298 addCommaBeforeField();
300 *mPos = '"';
301 ++mPos;
302 memcpy(mPos, pPropName, nPropNameLength);
303 mPos += nPropNameLength;
304 memcpy(mPos, "\": ", 3);
305 mPos += 3;
307 mPos += sprintf(mPos, "%" SAL_PRIdINT64, nPropVal);
310 void JsonWriter::put(const char* pPropName, double fPropVal)
312 OString sPropVal = rtl::math::doubleToString(fPropVal, rtl_math_StringFormat_F, 12, '.');
313 auto nPropNameLength = strlen(pPropName);
314 ensureSpace(nPropNameLength + sPropVal.getLength() + 8);
316 addCommaBeforeField();
318 *mPos = '"';
319 ++mPos;
320 memcpy(mPos, pPropName, nPropNameLength);
321 mPos += nPropNameLength;
322 memcpy(mPos, "\": ", 3);
323 mPos += 3;
325 memcpy(mPos, sPropVal.getStr(), sPropVal.getLength());
326 mPos += sPropVal.getLength();
329 void JsonWriter::put(const char* pPropName, bool nPropVal)
331 auto nPropNameLength = strlen(pPropName);
332 ensureSpace(nPropNameLength + 5 + 8);
334 addCommaBeforeField();
336 *mPos = '"';
337 ++mPos;
338 memcpy(mPos, pPropName, nPropNameLength);
339 mPos += nPropNameLength;
340 memcpy(mPos, "\": ", 3);
341 mPos += 3;
343 const char* pVal;
344 if (nPropVal)
345 pVal = "true";
346 else
347 pVal = "false";
348 memcpy(mPos, pVal, strlen(pVal));
349 mPos += strlen(pVal);
352 void JsonWriter::putSimpleValue(const OUString& rPropVal)
354 auto nWorstCasePropValLength = rPropVal.getLength() * 3;
355 ensureSpace(nWorstCasePropValLength + 4);
357 addCommaBeforeField();
359 *mPos = '"';
360 ++mPos;
362 writeEscapedOUString(rPropVal);
364 *mPos = '"';
365 ++mPos;
368 void JsonWriter::putRaw(std::string_view rRawBuf)
370 ensureSpace(rRawBuf.size() + 2);
372 addCommaBeforeField();
374 memcpy(mPos, rRawBuf.data(), rRawBuf.size());
375 mPos += rRawBuf.size();
378 void JsonWriter::addCommaBeforeField()
380 if (mbFirstFieldInNode)
381 mbFirstFieldInNode = false;
382 else
384 *mPos = ',';
385 ++mPos;
386 *mPos = ' ';
387 ++mPos;
391 void JsonWriter::ensureSpace(int noMoreBytesRequired)
393 assert(mpBuffer && "already extracted data");
394 int currentUsed = mPos - mpBuffer;
395 if (currentUsed + noMoreBytesRequired >= mSpaceAllocated)
397 auto newSize = (currentUsed + noMoreBytesRequired) * 2;
398 mpBuffer = static_cast<char*>(realloc(mpBuffer, newSize));
399 mPos = mpBuffer + currentUsed;
400 mSpaceAllocated = newSize;
404 /** Hands ownership of the underlying storage buffer to the caller,
405 * after this no more document modifications may be written. */
406 std::pair<char*, int> JsonWriter::extractDataImpl()
408 assert(mStartNodeCount == 0 && "did not close all nodes");
409 assert(mpBuffer && "data already extracted");
410 ensureSpace(2);
411 // add closing brace
412 *mPos = '}';
413 ++mPos;
414 // null-terminate
415 *mPos = 0;
416 const int sz = mPos - mpBuffer;
417 mPos = nullptr;
418 return { std::exchange(mpBuffer, nullptr), sz };
421 OString JsonWriter::extractAsOString()
423 auto[pChar, sz] = extractDataImpl();
424 OString ret(pChar, sz);
425 free(pChar);
426 return ret;
429 std::string JsonWriter::extractAsStdString()
431 auto[pChar, sz] = extractDataImpl();
432 std::string ret(pChar, sz);
433 free(pChar);
434 return ret;
437 bool JsonWriter::isDataEquals(const std::string& s) const
439 return s.length() == static_cast<size_t>(mPos - mpBuffer)
440 && memcmp(s.data(), mpBuffer, s.length()) == 0;
443 } // namespace tools
444 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */