1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
10 #include <tools/json_writer.hxx>
13 #include <rtl/math.hxx>
17 /** These buffers are short-lived, so rather waste some space and avoid the cost of
18 * repeated calls into the allocator */
19 constexpr int DEFAULT_BUFFER_SIZE
= 2048;
21 JsonWriter::JsonWriter()
22 : mpBuffer(static_cast<char*>(malloc(DEFAULT_BUFFER_SIZE
)))
24 , mSpaceAllocated(DEFAULT_BUFFER_SIZE
)
26 , mbFirstFieldInNode(true)
36 JsonWriter::~JsonWriter()
38 assert(!mpBuffer
&& "forgot to extract data?");
42 ScopedJsonWriterNode
JsonWriter::startNode(const char* pNodeName
)
44 auto len
= strlen(pNodeName
);
47 addCommaBeforeField();
51 memcpy(mPos
, pNodeName
, len
);
53 memcpy(mPos
, "\": { ", 5);
56 mbFirstFieldInNode
= true;
60 return ScopedJsonWriterNode(*this);
63 void JsonWriter::endNode()
65 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
70 mbFirstFieldInNode
= false;
75 ScopedJsonWriterArray
JsonWriter::startArray(const char* pNodeName
)
77 auto len
= strlen(pNodeName
);
80 addCommaBeforeField();
84 memcpy(mPos
, pNodeName
, len
);
86 memcpy(mPos
, "\": [ ", 5);
89 mbFirstFieldInNode
= true;
93 return ScopedJsonWriterArray(*this);
96 void JsonWriter::endArray()
98 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
103 mbFirstFieldInNode
= false;
108 ScopedJsonWriterStruct
JsonWriter::startStruct()
112 addCommaBeforeField();
119 mbFirstFieldInNode
= true;
123 return ScopedJsonWriterStruct(*this);
126 void JsonWriter::endStruct()
128 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
133 mbFirstFieldInNode
= false;
138 static char getEscapementChar(char ch
)
157 static bool writeEscapedSequence(sal_uInt32 ch
, char*& pos
)
159 // control characters
163 SAL_WNODEPRECATED_DECLARATIONS_PUSH
// sprintf (macOS 13 SDK)
164 int written
= sprintf(pos
, "\\u%.4x", ch
);
165 SAL_WNODEPRECATED_DECLARATIONS_POP
184 *pos
++ = getEscapementChar(ch
);
186 // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript
187 // Write them in escaped '\u2028' or '\u2029' form
195 *pos
++ = ch
== 0x2028 ? '8' : '9';
202 void JsonWriter::writeEscapedOUString(const OUString
& rPropVal
)
204 // Convert from UTF-16 to UTF-8 and perform escaping
206 while (i
< rPropVal
.getLength())
208 sal_uInt32 ch
= rPropVal
.iterateCodePoints(&i
);
209 if (writeEscapedSequence(ch
, mPos
))
213 *mPos
= static_cast<char>(ch
);
216 else if (ch
<= 0x7FF)
218 *mPos
= 0xC0 | (ch
>> 6); /* 110xxxxx */
220 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
223 else if (ch
<= 0xFFFF)
225 *mPos
= 0xE0 | (ch
>> 12); /* 1110xxxx */
227 *mPos
= 0x80 | ((ch
>> 6) & 0x3F); /* 10xxxxxx */
229 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
234 *mPos
= 0xF0 | (ch
>> 18); /* 11110xxx */
236 *mPos
= 0x80 | ((ch
>> 12) & 0x3F); /* 10xxxxxx */
238 *mPos
= 0x80 | ((ch
>> 6) & 0x3F); /* 10xxxxxx */
240 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
248 void JsonWriter::put(const OUString
& pPropName
, const OUString
& rPropVal
)
250 auto nPropNameLength
= pPropName
.getLength();
251 // But values can be any UTF-8,
252 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
253 auto nWorstCasePropValLength
= rPropVal
.getLength() * 6;
254 ensureSpace(nPropNameLength
+ nWorstCasePropValLength
+ 8);
256 addCommaBeforeField();
261 writeEscapedOUString(pPropName
);
263 memcpy(mPos
, "\": \"", 4);
266 writeEscapedOUString(rPropVal
);
274 void JsonWriter::put(const char* pPropName
, const OUString
& rPropVal
)
276 auto nPropNameLength
= strlen(pPropName
);
277 // But values can be any UTF-8,
278 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
279 auto nWorstCasePropValLength
= rPropVal
.getLength() * 6;
280 ensureSpace(nPropNameLength
+ nWorstCasePropValLength
+ 8);
282 addCommaBeforeField();
286 memcpy(mPos
, pPropName
, nPropNameLength
);
287 mPos
+= nPropNameLength
;
288 memcpy(mPos
, "\": \"", 4);
291 writeEscapedOUString(rPropVal
);
299 void JsonWriter::put(const char* pPropName
, std::string_view rPropVal
)
301 // we assume property names are ascii
302 auto nPropNameLength
= strlen(pPropName
);
303 // escaping can double the length
304 auto nWorstCasePropValLength
= rPropVal
.size() * 2;
305 ensureSpace(nPropNameLength
+ nWorstCasePropValLength
+ 8);
307 addCommaBeforeField();
311 memcpy(mPos
, pPropName
, nPropNameLength
);
312 mPos
+= nPropNameLength
;
313 memcpy(mPos
, "\": \"", 4);
316 // copy and perform escaping
317 bool bReachedEnd
= false;
318 for (size_t i
= 0; i
< rPropVal
.size() && !bReachedEnd
; ++i
)
320 char ch
= rPropVal
[i
];
331 writeEscapedSequence(ch
, mPos
);
336 case '\xE2': // Special processing of U+2028 and U+2029
337 if (i
+ 2 < rPropVal
.size() && rPropVal
[i
+ 1] == '\x80'
338 && (rPropVal
[i
+ 2] == '\xA8' || rPropVal
[i
+ 2] == '\xA9'))
340 writeEscapedSequence(rPropVal
[i
+ 2] == '\xA8' ? 0x2028 : 0x2029, mPos
);
358 void JsonWriter::put(const char* pPropName
, sal_Int64 nPropVal
)
360 auto nPropNameLength
= strlen(pPropName
);
361 auto nWorstCasePropValLength
= 32;
362 ensureSpace(nPropNameLength
+ nWorstCasePropValLength
+ 8);
364 addCommaBeforeField();
368 memcpy(mPos
, pPropName
, nPropNameLength
);
369 mPos
+= nPropNameLength
;
370 memcpy(mPos
, "\": ", 3);
374 SAL_WNODEPRECATED_DECLARATIONS_PUSH
// sprintf (macOS 13 SDK)
375 mPos
+= sprintf(mPos
, "%" SAL_PRIdINT64
, nPropVal
);
376 SAL_WNODEPRECATED_DECLARATIONS_POP
382 void JsonWriter::put(const char* pPropName
, double fPropVal
)
384 OString sPropVal
= rtl::math::doubleToString(fPropVal
, rtl_math_StringFormat_F
, 12, '.');
385 auto nPropNameLength
= strlen(pPropName
);
386 ensureSpace(nPropNameLength
+ sPropVal
.getLength() + 8);
388 addCommaBeforeField();
392 memcpy(mPos
, pPropName
, nPropNameLength
);
393 mPos
+= nPropNameLength
;
394 memcpy(mPos
, "\": ", 3);
397 memcpy(mPos
, sPropVal
.getStr(), sPropVal
.getLength());
398 mPos
+= sPropVal
.getLength();
403 void JsonWriter::put(const char* pPropName
, bool nPropVal
)
405 auto nPropNameLength
= strlen(pPropName
);
406 ensureSpace(nPropNameLength
+ 5 + 8);
408 addCommaBeforeField();
412 memcpy(mPos
, pPropName
, nPropNameLength
);
413 mPos
+= nPropNameLength
;
414 memcpy(mPos
, "\": ", 3);
422 memcpy(mPos
, pVal
, strlen(pVal
));
423 mPos
+= strlen(pVal
);
428 void JsonWriter::putSimpleValue(const OUString
& rPropVal
)
430 auto nWorstCasePropValLength
= rPropVal
.getLength() * 3;
431 ensureSpace(nWorstCasePropValLength
+ 4);
433 addCommaBeforeField();
438 writeEscapedOUString(rPropVal
);
446 void JsonWriter::putRaw(std::string_view rRawBuf
)
448 ensureSpace(rRawBuf
.size() + 2);
450 addCommaBeforeField();
452 memcpy(mPos
, rRawBuf
.data(), rRawBuf
.size());
453 mPos
+= rRawBuf
.size();
458 void JsonWriter::addCommaBeforeField()
460 if (mbFirstFieldInNode
)
461 mbFirstFieldInNode
= false;
471 void JsonWriter::ensureSpace(int noMoreBytesRequired
)
473 assert(mpBuffer
&& "already extracted data");
474 int currentUsed
= mPos
- mpBuffer
;
475 if (currentUsed
+ noMoreBytesRequired
>= mSpaceAllocated
)
477 auto newSize
= (currentUsed
+ noMoreBytesRequired
) * 2;
478 mpBuffer
= static_cast<char*>(realloc(mpBuffer
, newSize
));
479 mPos
= mpBuffer
+ currentUsed
;
480 mSpaceAllocated
= newSize
;
486 /** Hands ownership of the underlying storage buffer to the caller,
487 * after this no more document modifications may be written. */
488 std::pair
<char*, int> JsonWriter::extractDataImpl()
490 assert(mStartNodeCount
== 0 && "did not close all nodes");
491 assert(mpBuffer
&& "data already extracted");
498 const int sz
= mPos
- mpBuffer
;
500 return { std::exchange(mpBuffer
, nullptr), sz
};
503 OString
JsonWriter::extractAsOString()
505 auto[pChar
, sz
] = extractDataImpl();
506 OString
ret(pChar
, sz
);
511 std::string
JsonWriter::extractAsStdString()
513 auto[pChar
, sz
] = extractDataImpl();
514 std::string
ret(pChar
, sz
);
519 bool JsonWriter::isDataEquals(const std::string
& s
) const
521 return s
.length() == static_cast<size_t>(mPos
- mpBuffer
)
522 && memcmp(s
.data(), mpBuffer
, s
.length()) == 0;
526 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */