Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / tools / source / misc / json_writer.cxx
blob3111cac2f8167b64d9bb54fa8e3d827978d49611
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 <cstring>
13 #include <rtl/math.hxx>
15 namespace tools
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)))
23 , mPos(mpBuffer)
24 , mSpaceAllocated(DEFAULT_BUFFER_SIZE)
25 , mStartNodeCount(0)
26 , mbFirstFieldInNode(true)
27 , mbClosed(false)
29 *mPos = '{';
30 ++mPos;
31 *mPos = ' ';
32 ++mPos;
34 addValidationMark();
37 JsonWriter::~JsonWriter()
39 assert(mbClosed && "forgot to extract data?");
40 free(mpBuffer);
43 ScopedJsonWriterNode JsonWriter::startNode(std::string_view pNodeName)
45 putLiteral(pNodeName, "{ ");
47 mStartNodeCount++;
48 mbFirstFieldInNode = true;
50 return ScopedJsonWriterNode(*this);
53 void JsonWriter::endNode()
55 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
56 --mStartNodeCount;
57 ensureSpace(1);
58 *mPos = '}';
59 ++mPos;
60 mbFirstFieldInNode = false;
62 validate();
65 ScopedJsonWriterArray JsonWriter::startArray(std::string_view pNodeName)
67 putLiteral(pNodeName, "[ ");
69 mStartNodeCount++;
70 mbFirstFieldInNode = true;
72 return ScopedJsonWriterArray(*this);
75 void JsonWriter::endArray()
77 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
78 --mStartNodeCount;
79 ensureSpace(1);
80 *mPos = ']';
81 ++mPos;
82 mbFirstFieldInNode = false;
84 validate();
87 ScopedJsonWriterStruct JsonWriter::startStruct()
89 ensureSpace(6);
91 addCommaBeforeField();
93 *mPos = '{';
94 ++mPos;
95 *mPos = ' ';
96 ++mPos;
97 mStartNodeCount++;
98 mbFirstFieldInNode = true;
100 validate();
102 return ScopedJsonWriterStruct(*this);
105 void JsonWriter::endStruct()
107 assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
108 --mStartNodeCount;
109 ensureSpace(1);
110 *mPos = '}';
111 ++mPos;
112 mbFirstFieldInNode = false;
114 validate();
117 static char getEscapementChar(char ch)
119 switch (ch)
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;
136 static bool writeEscapedSequence(sal_uInt32 ch, char*& pos)
138 switch (ch)
140 case '\b':
141 case '\t':
142 case '\n':
143 case '\f':
144 case '\r':
145 case '"':
146 case '/':
147 case '\\':
148 *pos++ = '\\';
149 *pos++ = getEscapementChar(ch);
150 return true;
151 // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript
152 // Write them in escaped '\u2028' or '\u2029' form
153 case 0x2028:
154 case 0x2029:
155 *pos++ = '\\';
156 *pos++ = 'u';
157 *pos++ = '2';
158 *pos++ = '0';
159 *pos++ = '2';
160 *pos++ = ch == 0x2028 ? '8' : '9';
161 return true;
162 default:
163 return false;
167 void JsonWriter::writeEscapedOUString(const OUString& rPropVal)
169 *mPos = '"';
170 ++mPos;
172 // Convert from UTF-16 to UTF-8 and perform escaping
173 sal_Int32 i = 0;
174 while (i < rPropVal.getLength())
176 sal_uInt32 ch = rPropVal.iterateCodePoints(&i);
177 if (writeEscapedSequence(ch, mPos))
178 continue;
179 if (ch <= 0x7F)
181 *mPos = static_cast<char>(ch);
182 ++mPos;
184 else if (ch <= 0x7FF)
186 *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */
187 ++mPos;
188 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
189 ++mPos;
191 else if (ch <= 0xFFFF)
193 *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */
194 ++mPos;
195 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
196 ++mPos;
197 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
198 ++mPos;
200 else
202 *mPos = 0xF0 | (ch >> 18); /* 11110xxx */
203 ++mPos;
204 *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */
205 ++mPos;
206 *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
207 ++mPos;
208 *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
209 ++mPos;
213 *mPos = '"';
214 ++mPos;
216 validate();
219 void JsonWriter::put(std::string_view pPropName, const OUString& rPropVal)
221 // Values can be any UTF-8,
222 // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
223 auto nWorstCasePropValLength = rPropVal.getLength() * 6 + 2;
224 ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
226 writeEscapedOUString(rPropVal);
229 void JsonWriter::put(std::string_view pPropName, std::string_view rPropVal)
231 // escaping can double the length, plus quotes
232 auto nWorstCasePropValLength = rPropVal.size() * 2 + 2;
233 ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
235 *mPos = '"';
236 ++mPos;
238 // copy and perform escaping
239 bool bReachedEnd = false;
240 for (size_t i = 0; i < rPropVal.size() && !bReachedEnd; ++i)
242 char ch = rPropVal[i];
243 switch (ch)
245 case '\b':
246 case '\t':
247 case '\n':
248 case '\f':
249 case '\r':
250 case '"':
251 case '/':
252 case '\\':
253 writeEscapedSequence(ch, mPos);
254 break;
255 case 0:
256 bReachedEnd = true;
257 break;
258 case '\xE2': // Special processing of U+2028 and U+2029
259 if (i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80'
260 && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9'))
262 writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos);
263 i += 2;
264 break;
266 [[fallthrough]];
267 default:
268 *mPos = ch;
269 ++mPos;
270 break;
274 *mPos = '"';
275 ++mPos;
277 validate();
280 void JsonWriter::put(std::string_view pPropName, bool nPropVal)
282 putLiteral(pPropName, nPropVal ? std::string_view("true") : std::string_view("false"));
285 void JsonWriter::putSimpleValue(const OUString& rPropVal)
287 auto nWorstCasePropValLength = rPropVal.getLength() * 6;
288 ensureSpace(nWorstCasePropValLength + 4);
290 addCommaBeforeField();
292 writeEscapedOUString(rPropVal);
295 void JsonWriter::putRaw(std::string_view rRawBuf)
297 ensureSpace(rRawBuf.size() + 2);
299 addCommaBeforeField();
301 memcpy(mPos, rRawBuf.data(), rRawBuf.size());
302 mPos += rRawBuf.size();
304 validate();
307 void JsonWriter::addCommaBeforeField()
309 if (mbFirstFieldInNode)
310 mbFirstFieldInNode = false;
311 else
313 *mPos = ',';
314 ++mPos;
315 *mPos = ' ';
316 ++mPos;
320 void JsonWriter::ensureSpace(int noMoreBytesRequired)
322 assert(!mbClosed && "already extracted data");
323 int currentUsed = mPos - mpBuffer;
324 if (currentUsed + noMoreBytesRequired >= mSpaceAllocated)
326 auto newSize = (currentUsed + noMoreBytesRequired) * 2;
327 mpBuffer = static_cast<char*>(realloc(mpBuffer, newSize));
328 mPos = mpBuffer + currentUsed;
329 mSpaceAllocated = newSize;
331 addValidationMark();
335 void JsonWriter::ensureSpaceAndWriteNameColon(std::string_view name, int valSize)
337 // we assume property names are ascii
338 ensureSpace(name.size() + valSize + 6);
340 addCommaBeforeField();
342 *mPos = '"';
343 ++mPos;
344 memcpy(mPos, name.data(), name.size());
345 mPos += name.size();
346 memcpy(mPos, "\": ", 3);
347 mPos += 3;
350 void JsonWriter::putLiteral(std::string_view propName, std::string_view propValue)
352 ensureSpaceAndWriteNameColon(propName, propValue.size());
353 memcpy(mPos, propValue.data(), propValue.size());
354 mPos += propValue.size();
356 validate();
359 OString JsonWriter::finishAndGetAsOString()
361 assert(mStartNodeCount == 0 && "did not close all nodes");
362 assert(!mbClosed && "data already extracted");
363 ensureSpace(2);
364 // add closing brace
365 *mPos = '}';
366 ++mPos;
367 // null-terminate
368 *mPos = 0;
369 mbClosed = true;
371 OString ret(mpBuffer, mPos - mpBuffer);
372 return ret;
375 bool JsonWriter::isDataEquals(std::string_view s) const
377 return std::string_view(mpBuffer, static_cast<size_t>(mPos - mpBuffer)) == s;
380 } // namespace tools
381 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */