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