LibreOffice Module tools (master) 1
json_writer.cxx
Go to the documentation of this file.
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.
4 *
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 */
9
10#include <tools/json_writer.hxx>
11#include <stdio.h>
12#include <cstring>
13#include <rtl/math.hxx>
14
15namespace tools
16{
19constexpr int DEFAULT_BUFFER_SIZE = 2048;
20
22 : mpBuffer(static_cast<char*>(malloc(DEFAULT_BUFFER_SIZE)))
23 , mPos(mpBuffer)
24 , mSpaceAllocated(DEFAULT_BUFFER_SIZE)
25 , mStartNodeCount(0)
26 , mbFirstFieldInNode(true)
27 , mbClosed(false)
28{
29 *mPos = '{';
30 ++mPos;
31 *mPos = ' ';
32 ++mPos;
33
35}
36
38{
39 assert(mbClosed && "forgot to extract data?");
40 free(mpBuffer);
41}
42
44{
45 putLiteral(pNodeName, "{ ");
46
48 mbFirstFieldInNode = true;
49
50 return ScopedJsonWriterNode(*this);
51}
52
54{
55 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
57 ensureSpace(1);
58 *mPos = '}';
59 ++mPos;
60 mbFirstFieldInNode = false;
61
62 validate();
63}
64
66{
67 putLiteral(pNodeName, "[ ");
68
70 mbFirstFieldInNode = true;
71
72 return ScopedJsonWriterArray(*this);
73}
74
76{
77 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
79 ensureSpace(1);
80 *mPos = ']';
81 ++mPos;
82 mbFirstFieldInNode = false;
83
84 validate();
85}
86
88{
89 ensureSpace(6);
90
92
93 *mPos = '{';
94 ++mPos;
95 *mPos = ' ';
96 ++mPos;
98 mbFirstFieldInNode = true;
99
100 validate();
101
102 return ScopedJsonWriterStruct(*this);
103}
104
106{
107 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
109 ensureSpace(1);
110 *mPos = '}';
111 ++mPos;
112 mbFirstFieldInNode = false;
113
114 validate();
115}
116
117static char getEscapementChar(char ch)
118{
119 switch (ch)
120 {
121 case '\b':
122 return 'b';
123 case '\t':
124 return 't';
125 case '\n':
126 return 'n';
127 case '\f':
128 return 'f';
129 case '\r':
130 return 'r';
131 default:
132 return ch;
133 }
134}
135
136static bool writeEscapedSequence(sal_uInt32 ch, char*& pos)
137{
138 // control characters
139 if (ch <= 0x1f)
140 {
141 int written = snprintf(pos, 7, "\\u%.4x", static_cast<unsigned int>(ch));
142 if (written > 0)
143 pos += written;
144 return true;
145 }
146
147 switch (ch)
148 {
149 case '"':
150 case '/':
151 case '\\':
152 *pos++ = '\\';
154 return true;
155 // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript
156 // Write them in escaped '\u2028' or '\u2029' form
157 case 0x2028:
158 case 0x2029:
159 *pos++ = '\\';
160 *pos++ = 'u';
161 *pos++ = '2';
162 *pos++ = '0';
163 *pos++ = '2';
164 *pos++ = ch == 0x2028 ? '8' : '9';
165 return true;
166 default:
167 return false;
168 }
169}
170
171void JsonWriter::writeEscapedOUString(const OUString& rPropVal)
172{
173 *mPos = '"';
174 ++mPos;
175
176 // Convert from UTF-16 to UTF-8 and perform escaping
177 sal_Int32 i = 0;
178 while (i < rPropVal.getLength())
179 {
180 sal_uInt32 ch = rPropVal.iterateCodePoints(&i);
182 continue;
183 if (ch <= 0x7F)
184 {
185 *mPos = static_cast<char>(ch);
186 ++mPos;
187 }
188 else if (ch <= 0x7FF)
189 {
190 *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */
191 ++mPos;
192 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
193 ++mPos;
194 }
195 else if (ch <= 0xFFFF)
196 {
197 *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */
198 ++mPos;
199 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
200 ++mPos;
201 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
202 ++mPos;
203 }
204 else
205 {
206 *mPos = 0xF0 | (ch >> 18); /* 11110xxx */
207 ++mPos;
208 *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */
209 ++mPos;
210 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
211 ++mPos;
212 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
213 ++mPos;
214 }
215 }
216
217 *mPos = '"';
218 ++mPos;
219
220 validate();
221}
222
223void JsonWriter::put(std::u16string_view pPropName, const OUString& rPropVal)
224{
225 auto nPropNameLength = pPropName.length();
226 // But values can be any UTF-8,
227 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
228 auto nWorstCasePropValLength = rPropVal.getLength() * 6;
229 ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
230
232
233 writeEscapedOUString(OUString(pPropName));
234
235 memcpy(mPos, ": ", 2);
236 mPos += 2;
237
238 writeEscapedOUString(rPropVal);
239
240 validate();
241}
242
243void JsonWriter::put(std::string_view pPropName, const OUString& rPropVal)
244{
245 // Values can be any UTF-8,
246 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
247 auto nWorstCasePropValLength = rPropVal.getLength() * 6 + 2;
248 ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
249
250 writeEscapedOUString(rPropVal);
251}
252
253void JsonWriter::put(std::string_view pPropName, std::string_view rPropVal)
254{
255 // escaping can double the length, plus quotes
256 auto nWorstCasePropValLength = rPropVal.size() * 2 + 2;
257 ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
258
259 *mPos = '"';
260 ++mPos;
261
262 // copy and perform escaping
263 bool bReachedEnd = false;
264 for (size_t i = 0; i < rPropVal.size() && !bReachedEnd; ++i)
265 {
266 char ch = rPropVal[i];
267 switch (ch)
268 {
269 case '\b':
270 case '\t':
271 case '\n':
272 case '\f':
273 case '\r':
274 case '"':
275 case '/':
276 case '\\':
278 break;
279 case 0:
280 bReachedEnd = true;
281 break;
282 case '\xE2': // Special processing of U+2028 and U+2029
283 if (i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80'
284 && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9'))
285 {
286 writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos);
287 i += 2;
288 break;
289 }
290 [[fallthrough]];
291 default:
292 *mPos = ch;
293 ++mPos;
294 break;
295 }
296 }
297
298 *mPos = '"';
299 ++mPos;
300
301 validate();
302}
303
304void JsonWriter::put(std::string_view pPropName, bool nPropVal)
305{
306 putLiteral(pPropName, nPropVal ? std::string_view("true") : std::string_view("false"));
307}
308
309void JsonWriter::putSimpleValue(const OUString& rPropVal)
310{
311 auto nWorstCasePropValLength = rPropVal.getLength() * 6;
312 ensureSpace(nWorstCasePropValLength + 4);
313
315
316 writeEscapedOUString(rPropVal);
317}
318
319void JsonWriter::putRaw(std::string_view rRawBuf)
320{
321 ensureSpace(rRawBuf.size() + 2);
322
324
325 memcpy(mPos, rRawBuf.data(), rRawBuf.size());
326 mPos += rRawBuf.size();
327
328 validate();
329}
330
332{
334 mbFirstFieldInNode = false;
335 else
336 {
337 *mPos = ',';
338 ++mPos;
339 *mPos = ' ';
340 ++mPos;
341 }
342}
343
344void JsonWriter::ensureSpace(int noMoreBytesRequired)
345{
346 assert(!mbClosed && "already extracted data");
347 int currentUsed = mPos - mpBuffer;
348 if (currentUsed + noMoreBytesRequired >= mSpaceAllocated)
349 {
350 auto newSize = (currentUsed + noMoreBytesRequired) * 2;
351 mpBuffer = static_cast<char*>(realloc(mpBuffer, newSize));
352 mPos = mpBuffer + currentUsed;
353 mSpaceAllocated = newSize;
354
356 }
357}
358
359void JsonWriter::ensureSpaceAndWriteNameColon(std::string_view name, int valSize)
360{
361 // we assume property names are ascii
362 ensureSpace(name.size() + valSize + 6);
363
365
366 *mPos = '"';
367 ++mPos;
368 memcpy(mPos, name.data(), name.size());
369 mPos += name.size();
370 memcpy(mPos, "\": ", 3);
371 mPos += 3;
372}
373
374void JsonWriter::putLiteral(std::string_view propName, std::string_view propValue)
375{
376 ensureSpaceAndWriteNameColon(propName, propValue.size());
377 memcpy(mPos, propValue.data(), propValue.size());
378 mPos += propValue.size();
379
380 validate();
381}
382
384{
385 assert(mStartNodeCount == 0 && "did not close all nodes");
386 assert(!mbClosed && "data already extracted");
387 ensureSpace(2);
388 // add closing brace
389 *mPos = '}';
390 ++mPos;
391 // null-terminate
392 *mPos = 0;
393 mbClosed = true;
394
395 OString ret(mpBuffer, mPos - mpBuffer);
396 return ret;
397}
398
399bool JsonWriter::isDataEquals(std::string_view s) const
400{
401 return std::string_view(mpBuffer, static_cast<size_t>(mPos - mpBuffer)) == s;
402}
403
404} // namespace tools
405/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
void putLiteral(std::string_view propName, std::string_view propValue)
void writeEscapedOUString(const OUString &rPropVal)
void ensureSpace(int noMoreBytesRequired)
void put(std::u16string_view pPropName, const OUString &rPropValue)
void ensureSpaceAndWriteNameColon(std::string_view name, int valSize)
ScopedJsonWriterStruct startStruct()
Definition: json_writer.cxx:87
void putSimpleValue(const OUString &rPropValue)
friend class ScopedJsonWriterNode
Definition: json_writer.hxx:36
OString finishAndGetAsOString()
Closes the tags, and returns data.
ScopedJsonWriterNode startNode(std::string_view)
Definition: json_writer.cxx:43
friend class ScopedJsonWriterStruct
Definition: json_writer.hxx:38
bool isDataEquals(std::string_view) const
returns true if the current JSON data matches the string
void putRaw(std::string_view)
This assumes that this data belongs at this point in the stream, and is valid, and properly encoded.
friend class ScopedJsonWriterArray
Definition: json_writer.hxx:37
ScopedJsonWriterArray startArray(std::string_view)
Definition: json_writer.cxx:65
void addCommaBeforeField()
Auto-closes the node.
Auto-closes the node.
Auto-closes the node.
const char * name
int i
Note: this class is a true marvel of engineering: because the author could not decide whether it's be...
constexpr int DEFAULT_BUFFER_SIZE
These buffers are short-lived, so rather waste some space and avoid the cost of repeated calls into t...
Definition: json_writer.cxx:19
static char getEscapementChar(char ch)
static bool writeEscapedSequence(sal_uInt32 ch, char *&pos)
size_t pos