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