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 dbg_assert(str_utf8_check(pStr), "CJsonWriter::WriteInternalEscaped: input must be valid UTF-8");
115
116 WriteInternal(pStr: "\"");
117 int UnwrittenFrom = 0;
118 int Length = str_length(str: pStr);
119 for(int i = 0; i < Length; i++)
120 {
121 char SimpleEscape = EscapeJsonChar(c: pStr[i]);
122 // Assuming ASCII/UTF-8, exactly everything below 0x20 is a
123 // control character.
124 bool NeedsEscape = SimpleEscape || (unsigned char)pStr[i] < 0x20;
125 if(NeedsEscape)
126 {
127 if(i - UnwrittenFrom > 0)
128 {
129 WriteInternal(pStr: pStr + UnwrittenFrom, Length: i - UnwrittenFrom);
130 }
131
132 if(SimpleEscape)
133 {
134 char aStr[2];
135 aStr[0] = '\\';
136 aStr[1] = SimpleEscape;
137 WriteInternal(pStr: aStr, Length: sizeof(aStr));
138 }
139 else
140 {
141 char aStr[7];
142 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "\\u%04x", pStr[i]);
143 WriteInternal(pStr: aStr);
144 }
145 UnwrittenFrom = i + 1;
146 }
147 }
148 if(Length - UnwrittenFrom > 0)
149 {
150 WriteInternal(pStr: pStr + UnwrittenFrom, Length: Length - UnwrittenFrom);
151 }
152 WriteInternal(pStr: "\"");
153}
154
155void CJsonWriter::WriteIndent(bool EndElement)
156{
157 const bool NotRootOrAttribute = !m_States.empty() && TopState()->m_Kind != STATE_ATTRIBUTE;
158
159 if(NotRootOrAttribute && !TopState()->m_Empty && !EndElement)
160 WriteInternal(pStr: ",");
161
162 if(NotRootOrAttribute || EndElement)
163 WriteInternal(pStr: "\n");
164
165 if(NotRootOrAttribute)
166 for(int i = 0; i < m_Indentation; i++)
167 WriteInternal(pStr: "\t");
168}
169
170void CJsonWriter::PushState(EJsonStateKind NewState)
171{
172 if(!m_States.empty())
173 {
174 m_States.top().m_Empty = false;
175 }
176 m_States.emplace(args&: NewState);
177 if(NewState != STATE_ATTRIBUTE)
178 {
179 m_Indentation++;
180 }
181}
182
183CJsonWriter::SState *CJsonWriter::TopState()
184{
185 dbg_assert(!m_States.empty(), "json stack is empty");
186 return &m_States.top();
187}
188
189CJsonWriter::EJsonStateKind CJsonWriter::PopState()
190{
191 dbg_assert(!m_States.empty(), "json stack is empty");
192 SState TopState = m_States.top();
193 m_States.pop();
194 if(TopState.m_Kind != STATE_ATTRIBUTE)
195 {
196 m_Indentation--;
197 }
198 return TopState.m_Kind;
199}
200
201void CJsonWriter::CompleteDataType()
202{
203 if(!m_States.empty() && TopState()->m_Kind == STATE_ATTRIBUTE)
204 PopState(); // automatically complete the attribute
205
206 if(!m_States.empty())
207 TopState()->m_Empty = false;
208}
209
210CJsonFileWriter::CJsonFileWriter(IOHANDLE IO)
211{
212 dbg_assert((bool)IO, "IO handle invalid");
213 m_IO = IO;
214}
215
216CJsonFileWriter::~CJsonFileWriter()
217{
218 // Ensure newline at the end
219 WriteInternal(pStr: "\n");
220 io_close(io: m_IO);
221}
222
223void CJsonFileWriter::WriteInternal(const char *pStr, int Length)
224{
225 io_write(io: m_IO, buffer: pStr, size: Length < 0 ? str_length(str: pStr) : Length);
226}
227
228void CJsonStringWriter::WriteInternal(const char *pStr, int Length)
229{
230 dbg_assert(!m_RetrievedOutput, "Writer output has already been retrieved");
231 m_OutputString += Length < 0 ? pStr : std::string(pStr, Length);
232}
233
234std::string &&CJsonStringWriter::GetOutputString()
235{
236 // Ensure newline at the end. Modify member variable so we can move it when returning.
237 WriteInternal(pStr: "\n");
238 m_RetrievedOutput = true; // prevent further usage of this writer
239 return std::move(m_OutputString);
240}
241