1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "jsonwriter.h"
5
6#include <base/dbg.h>
7#include <base/io.h>
8#include <base/str.h>
9
10static char EscapeJsonChar(char c)
11{
12 switch(c)
13 {
14 case '\"': return '\"';
15 case '\\': return '\\';
16 case '\b': return 'b';
17 case '\n': return 'n';
18 case '\r': return 'r';
19 case '\t': return 't';
20 // Don't escape '\f', who uses that. :)
21 default: return 0;
22 }
23}
24
25CJsonWriter::CJsonWriter()
26{
27 m_Indentation = 0;
28}
29
30void CJsonWriter::BeginObject()
31{
32 dbg_assert(CanWriteDatatype(), "Cannot write object here");
33 WriteIndent(EndElement: false);
34 WriteInternal(pStr: "{");
35 PushState(NewState: STATE_OBJECT);
36}
37
38void CJsonWriter::EndObject()
39{
40 dbg_assert(TopState()->m_Kind == STATE_OBJECT, "Cannot end object here");
41 PopState();
42 CompleteDataType();
43 WriteIndent(EndElement: true);
44 WriteInternal(pStr: "}");
45}
46
47void CJsonWriter::BeginArray()
48{
49 dbg_assert(CanWriteDatatype(), "Cannot write array here");
50 WriteIndent(EndElement: false);
51 WriteInternal(pStr: "[");
52 PushState(NewState: STATE_ARRAY);
53}
54
55void CJsonWriter::EndArray()
56{
57 dbg_assert(TopState()->m_Kind == STATE_ARRAY, "Cannot end array here");
58 PopState();
59 CompleteDataType();
60 WriteIndent(EndElement: true);
61 WriteInternal(pStr: "]");
62}
63
64void CJsonWriter::WriteAttribute(const char *pName)
65{
66 dbg_assert(TopState()->m_Kind == STATE_OBJECT, "Cannot write attribute here");
67 WriteIndent(EndElement: false);
68 WriteInternalEscaped(pStr: pName);
69 WriteInternal(pStr: ": ");
70 PushState(NewState: STATE_ATTRIBUTE);
71}
72
73void CJsonWriter::WriteStrValue(const char *pValue)
74{
75 dbg_assert(CanWriteDatatype(), "Cannot write value here");
76 WriteIndent(EndElement: false);
77 WriteInternalEscaped(pStr: pValue);
78 CompleteDataType();
79}
80
81void CJsonWriter::WriteIntValue(int Value)
82{
83 dbg_assert(CanWriteDatatype(), "Cannot write value here");
84 WriteIndent(EndElement: false);
85 char aBuf[32];
86 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", Value);
87 WriteInternal(pStr: aBuf);
88 CompleteDataType();
89}
90
91void CJsonWriter::WriteBoolValue(bool Value)
92{
93 dbg_assert(CanWriteDatatype(), "Cannot write value here");
94 WriteIndent(EndElement: false);
95 WriteInternal(pStr: Value ? "true" : "false");
96 CompleteDataType();
97}
98
99void CJsonWriter::WriteNullValue()
100{
101 dbg_assert(CanWriteDatatype(), "Cannot write value here");
102 WriteIndent(EndElement: false);
103 WriteInternal(pStr: "null");
104 CompleteDataType();
105}
106
107bool CJsonWriter::CanWriteDatatype()
108{
109 return m_States.empty() || TopState()->m_Kind == STATE_ARRAY || TopState()->m_Kind == STATE_ATTRIBUTE;
110}
111
112void CJsonWriter::WriteInternalEscaped(const char *pStr)
113{
114 WriteInternal(pStr: "\"");
115 int UnwrittenFrom = 0;
116 int Length = str_length(str: pStr);
117 for(int i = 0; i < Length; i++)
118 {
119 char SimpleEscape = EscapeJsonChar(c: pStr[i]);
120 // Assuming ASCII/UTF-8, exactly everything below 0x20 is a
121 // control character.
122 bool NeedsEscape = SimpleEscape || (unsigned char)pStr[i] < 0x20;
123 if(NeedsEscape)
124 {
125 if(i - UnwrittenFrom > 0)
126 {
127 WriteInternal(pStr: pStr + UnwrittenFrom, Length: i - UnwrittenFrom);
128 }
129
130 if(SimpleEscape)
131 {
132 char aStr[2];
133 aStr[0] = '\\';
134 aStr[1] = SimpleEscape;
135 WriteInternal(pStr: aStr, Length: sizeof(aStr));
136 }
137 else
138 {
139 char aStr[7];
140 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "\\u%04x", pStr[i]);
141 WriteInternal(pStr: aStr);
142 }
143 UnwrittenFrom = i + 1;
144 }
145 }
146 if(Length - UnwrittenFrom > 0)
147 {
148 WriteInternal(pStr: pStr + UnwrittenFrom, Length: Length - UnwrittenFrom);
149 }
150 WriteInternal(pStr: "\"");
151}
152
153void CJsonWriter::WriteIndent(bool EndElement)
154{
155 const bool NotRootOrAttribute = !m_States.empty() && TopState()->m_Kind != STATE_ATTRIBUTE;
156
157 if(NotRootOrAttribute && !TopState()->m_Empty && !EndElement)
158 WriteInternal(pStr: ",");
159
160 if(NotRootOrAttribute || EndElement)
161 WriteInternal(pStr: "\n");
162
163 if(NotRootOrAttribute)
164 for(int i = 0; i < m_Indentation; i++)
165 WriteInternal(pStr: "\t");
166}
167
168void CJsonWriter::PushState(EJsonStateKind NewState)
169{
170 if(!m_States.empty())
171 {
172 m_States.top().m_Empty = false;
173 }
174 m_States.emplace(args&: NewState);
175 if(NewState != STATE_ATTRIBUTE)
176 {
177 m_Indentation++;
178 }
179}
180
181CJsonWriter::SState *CJsonWriter::TopState()
182{
183 dbg_assert(!m_States.empty(), "json stack is empty");
184 return &m_States.top();
185}
186
187CJsonWriter::EJsonStateKind CJsonWriter::PopState()
188{
189 dbg_assert(!m_States.empty(), "json stack is empty");
190 SState TopState = m_States.top();
191 m_States.pop();
192 if(TopState.m_Kind != STATE_ATTRIBUTE)
193 {
194 m_Indentation--;
195 }
196 return TopState.m_Kind;
197}
198
199void CJsonWriter::CompleteDataType()
200{
201 if(!m_States.empty() && TopState()->m_Kind == STATE_ATTRIBUTE)
202 PopState(); // automatically complete the attribute
203
204 if(!m_States.empty())
205 TopState()->m_Empty = false;
206}
207
208CJsonFileWriter::CJsonFileWriter(IOHANDLE IO)
209{
210 dbg_assert((bool)IO, "IO handle invalid");
211 m_IO = IO;
212}
213
214CJsonFileWriter::~CJsonFileWriter()
215{
216 // Ensure newline at the end
217 WriteInternal(pStr: "\n");
218 io_close(io: m_IO);
219}
220
221void CJsonFileWriter::WriteInternal(const char *pStr, int Length)
222{
223 io_write(io: m_IO, buffer: pStr, size: Length < 0 ? str_length(str: pStr) : Length);
224}
225
226void CJsonStringWriter::WriteInternal(const char *pStr, int Length)
227{
228 dbg_assert(!m_RetrievedOutput, "Writer output has already been retrieved");
229 m_OutputString += Length < 0 ? pStr : std::string(pStr, Length);
230}
231
232std::string &&CJsonStringWriter::GetOutputString()
233{
234 // Ensure newline at the end. Modify member variable so we can move it when returning.
235 WriteInternal(pStr: "\n");
236 m_RetrievedOutput = true; // prevent further usage of this writer
237 return std::move(m_OutputString);
238}
239