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>
14 #include <rtl/math.hxx>
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
)))
25 , mSpaceAllocated(DEFAULT_BUFFER_SIZE
)
27 , mbFirstFieldInNode(true)
35 JsonWriter::~JsonWriter()
37 assert(!mpBuffer
&& "forgot to extract data?");
41 ScopedJsonWriterNode
JsonWriter::startNode(const char* pNodeName
)
43 auto len
= strlen(pNodeName
);
46 addCommaBeforeField();
50 memcpy(mPos
, pNodeName
, len
);
52 memcpy(mPos
, "\": { ", 5);
55 mbFirstFieldInNode
= true;
56 return ScopedJsonWriterNode(*this);
59 void JsonWriter::endNode()
61 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
66 mbFirstFieldInNode
= false;
69 ScopedJsonWriterArray
JsonWriter::startArray(const char* pNodeName
)
71 auto len
= strlen(pNodeName
);
74 addCommaBeforeField();
78 memcpy(mPos
, pNodeName
, len
);
80 memcpy(mPos
, "\": [ ", 5);
83 mbFirstFieldInNode
= true;
84 return ScopedJsonWriterArray(*this);
87 void JsonWriter::endArray()
89 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
94 mbFirstFieldInNode
= false;
97 ScopedJsonWriterStruct
JsonWriter::startStruct()
101 addCommaBeforeField();
108 mbFirstFieldInNode
= true;
109 return ScopedJsonWriterStruct(*this);
112 void JsonWriter::endStruct()
114 assert(mStartNodeCount
&& "mismatched StartNode/EndNode somewhere");
119 mbFirstFieldInNode
= false;
122 static char getEscapementChar(char ch
)
141 static bool writeEscapedSequence(sal_uInt32 ch
, char*& pos
)
154 *pos
++ = getEscapementChar(ch
);
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
165 *pos
++ = ch
== 0x2028 ? '8' : '9';
172 void JsonWriter::writeEscapedOUString(const OUString
& rPropVal
)
174 // Convert from UTF-16 to UTF-8 and perform escaping
176 while (i
< rPropVal
.getLength())
178 sal_uInt32 ch
= rPropVal
.iterateCodePoints(&i
);
179 if (writeEscapedSequence(ch
, mPos
))
183 *mPos
= static_cast<char>(ch
);
186 else if (ch
<= 0x7FF)
188 *mPos
= 0xC0 | (ch
>> 6); /* 110xxxxx */
190 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
193 else if (ch
<= 0xFFFF)
195 *mPos
= 0xE0 | (ch
>> 12); /* 1110xxxx */
197 *mPos
= 0x80 | ((ch
>> 6) & 0x3F); /* 10xxxxxx */
199 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
204 *mPos
= 0xF0 | (ch
>> 18); /* 11110xxx */
206 *mPos
= 0x80 | ((ch
>> 12) & 0x3F); /* 10xxxxxx */
208 *mPos
= 0x80 | ((ch
>> 6) & 0x3F); /* 10xxxxxx */
210 *mPos
= 0x80 | (ch
& 0x3F); /* 10xxxxxx */
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();
228 memcpy(mPos
, pPropName
, nPropNameLength
);
229 mPos
+= nPropNameLength
;
230 memcpy(mPos
, "\": \"", 4);
233 writeEscapedOUString(rPropVal
);
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();
251 memcpy(mPos
, pPropName
, nPropNameLength
);
252 mPos
+= nPropNameLength
;
253 memcpy(mPos
, "\": \"", 4);
256 // copy and perform escaping
257 for (size_t i
= 0; i
< rPropVal
.size(); ++i
)
259 char ch
= rPropVal
[i
];
270 writeEscapedSequence(ch
, mPos
);
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
);
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();
302 memcpy(mPos
, pPropName
, nPropNameLength
);
303 mPos
+= nPropNameLength
;
304 memcpy(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();
320 memcpy(mPos
, pPropName
, nPropNameLength
);
321 mPos
+= nPropNameLength
;
322 memcpy(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();
338 memcpy(mPos
, pPropName
, nPropNameLength
);
339 mPos
+= nPropNameLength
;
340 memcpy(mPos
, "\": ", 3);
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();
362 writeEscapedOUString(rPropVal
);
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;
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");
416 const int sz
= mPos
- mpBuffer
;
418 return { std::exchange(mpBuffer
, nullptr), sz
};
421 OString
JsonWriter::extractAsOString()
423 auto[pChar
, sz
] = extractDataImpl();
424 OString
ret(pChar
, sz
);
429 std::string
JsonWriter::extractAsStdString()
431 auto[pChar
, sz
] = extractDataImpl();
432 std::string
ret(pChar
, sz
);
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;
444 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */