sjson.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. /*
  6. * Original C# code:
  7. * Public Domain Niklas Frykholm
  8. */
  9. namespace Crown
  10. {
  11. /// <summary>
  12. /// Provides functions for encoding and decoding files in the simplified JSON format.
  13. /// </summary>
  14. [Compact]
  15. public class SJSON
  16. {
  17. /// <summary>
  18. /// Encodes the Hashtable t in the simplified JSON format. The Hashtable can
  19. /// contain, numbers, bools, strings, ArrayLists and Hashtables.
  20. /// </summary>
  21. public static string encode(Hashtable t) throws JsonWriteError
  22. {
  23. StringBuilder sb = new StringBuilder();
  24. write_root_object(t, sb);
  25. sb.append_c('\n');
  26. return sb.str;
  27. }
  28. /// <summary>
  29. /// Encodes the object o in the simplified JSON format (not as a root object).
  30. /// </summary>
  31. public static string encode_object(Value? o) throws JsonWriteError
  32. {
  33. StringBuilder sb = new StringBuilder();
  34. write(o, sb, 0);
  35. return sb.str;
  36. }
  37. /// <summary>
  38. /// Decodes a SJSON bytestream into a Hashtable with numbers, bools, strings,
  39. /// ArrayLists and Hashtables.
  40. /// </summary>
  41. public static Hashtable decode(uint8[] sjson) throws JsonSyntaxError
  42. {
  43. int index = 0;
  44. return parse_root_object(sjson, ref index);
  45. }
  46. /// <summary>
  47. /// Convenience function for loading a file.
  48. /// </summary>
  49. public static Hashtable load_from_file(GLib.FileStream? fs) throws JsonSyntaxError
  50. {
  51. if (fs == null)
  52. return new Hashtable();
  53. // Get file size
  54. fs.seek(0, FileSeek.END);
  55. size_t size = fs.tell();
  56. fs.rewind();
  57. if (size == 0)
  58. return new Hashtable();
  59. // Read whole file
  60. uint8[] bytes = new uint8[size];
  61. size_t bytes_read = fs.read(bytes);
  62. if (bytes_read != size)
  63. return new Hashtable();
  64. return decode(bytes) as Hashtable;
  65. }
  66. /// <summary>
  67. /// Convenience function for loading a file.
  68. /// </summary>
  69. public static Hashtable load_from_path(string path) throws JsonSyntaxError
  70. {
  71. FileStream fs = FileStream.open(path, "rb");
  72. return load_from_file(fs);
  73. }
  74. /// <summary>
  75. /// Convenience function for saving a file.
  76. /// </summary>
  77. public static void save(Hashtable h, string path) throws JsonWriteError
  78. {
  79. FileStream fs = FileStream.open(path, "wb");
  80. if (fs == null)
  81. throw new JsonWriteError.FILE_OPEN("Unable to open '%s'".printf(path));
  82. uint8[] data = encode(h).data;
  83. size_t len = data.length;
  84. if (fs.write(data) != len)
  85. throw new JsonWriteError.FILE_WRITE("Error while writing '%s'".printf(path));
  86. }
  87. static void write_root_object(Hashtable t, StringBuilder builder) throws JsonWriteError
  88. {
  89. write_object_fields(t, builder, 0);
  90. }
  91. static void write_object_fields(Hashtable t, StringBuilder builder, int indentation) throws JsonWriteError
  92. {
  93. Gee.ArrayList<string> keys = new Gee.ArrayList<string>.wrap(t.keys.to_array());
  94. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  95. foreach (string key in keys) {
  96. write_new_line(builder, indentation);
  97. builder.append(key);
  98. builder.append(" = ");
  99. write(t[key], builder, indentation);
  100. }
  101. }
  102. static void write_new_line(StringBuilder builder, int indentation)
  103. {
  104. if (builder.len > 0)
  105. builder.append_c('\n');
  106. for (int i = 0; i < indentation; ++i)
  107. builder.append_c('\t');
  108. }
  109. static void write(Value? o, StringBuilder builder, int indentation) throws JsonWriteError
  110. {
  111. if (o == null)
  112. builder.append("null");
  113. else if (o.holds(typeof(bool)) && (bool)o == false)
  114. builder.append("false");
  115. else if (o.holds(typeof(bool)))
  116. builder.append("true");
  117. else if (o.holds(typeof(uint8)))
  118. builder.append_printf("%u", (uint8)o);
  119. else if (o.holds(typeof(int)))
  120. builder.append_printf("%d", (int)o);
  121. else if (o.holds(typeof(float)))
  122. builder.append_printf("%.9g", (float)o);
  123. else if (o.holds(typeof(double)))
  124. builder.append_printf("%.17g", (double)o);
  125. else if (o.holds(typeof(string)))
  126. write_string((string)o, builder);
  127. else if (o.holds(typeof(Gee.ArrayList)))
  128. write_array((Gee.ArrayList)o, builder, indentation);
  129. else if (o.holds(typeof(Hashtable)))
  130. write_object((Hashtable)o, builder, indentation);
  131. else
  132. throw new JsonWriteError.BAD_VALUE("Unsupported value type '%s'".printf(o.type_name()));
  133. }
  134. static void write_string(string s, StringBuilder builder)
  135. {
  136. builder.append_c('"');
  137. for (int i = 0; i < s.length; ++i) {
  138. char c = s[i];
  139. if (c == '"' || c == '\\')
  140. builder.append_c('\\');
  141. builder.append_c(c);
  142. }
  143. builder.append_c('"');
  144. }
  145. static void write_array(Gee.ArrayList<Value?> a, StringBuilder builder, int indentation) throws JsonWriteError
  146. {
  147. Gee.ArrayList<Value?> a_sorted = a;
  148. a_sorted.sort((a, b) => {
  149. if (!a.holds(typeof(Hashtable)) || !b.holds(typeof(Hashtable)))
  150. return 0;
  151. Hashtable obj_a = a as Hashtable;
  152. Hashtable obj_b = b as Hashtable;
  153. string guid_a_str;
  154. string guid_b_str;
  155. if (obj_a.has_key("id")) {
  156. Value? val = obj_a["id"];
  157. if (val.holds(typeof(string)))
  158. guid_a_str = (string)val;
  159. else
  160. // The 'id' key has been used for something else than a Guid. Font files are
  161. // an example of the 'id' key used to store the codepoint of a glyph.
  162. return 0;
  163. } else if (obj_a.has_key("_guid")) {
  164. guid_a_str = (string)obj_a["_guid"];
  165. } else {
  166. return 0;
  167. }
  168. if (obj_b.has_key("id")) {
  169. Value? val = obj_b["id"];
  170. if (val.holds(typeof(string)))
  171. guid_b_str = (string)val;
  172. else
  173. return 0; // See comment above.
  174. } else if (obj_b.has_key("_guid")) {
  175. guid_b_str = (string)obj_b["_guid"];
  176. } else {
  177. return 0;
  178. }
  179. Guid guid_a = Guid.parse(guid_a_str);
  180. Guid guid_b = Guid.parse(guid_b_str);
  181. return Guid.compare_func(guid_a, guid_b);
  182. });
  183. builder.append_c('[');
  184. foreach (Value? item in a_sorted) {
  185. write_new_line(builder, indentation + 1);
  186. write(item, builder, indentation + 1);
  187. }
  188. write_new_line(builder, indentation);
  189. builder.append_c(']');
  190. }
  191. static void write_object(Hashtable t, StringBuilder builder, int indentation) throws JsonWriteError
  192. {
  193. builder.append_c('{');
  194. write_object_fields(t, builder, indentation + 1);
  195. write_new_line(builder, indentation);
  196. builder.append_c('}');
  197. }
  198. static Hashtable parse_root_object(uint8 [] json, ref int index) throws JsonSyntaxError
  199. {
  200. Hashtable ht = new Hashtable();
  201. while (!at_end(json, ref index)) {
  202. string key = parse_identifier(json, ref index);
  203. consume(json, ref index, "=");
  204. Value? value = parse_value(json, ref index);
  205. ht[key] = value;
  206. }
  207. return ht;
  208. }
  209. static bool at_end(uint8 [] json, ref int index) throws JsonSyntaxError
  210. {
  211. skip_whitespace(json, ref index);
  212. return index >= json.length;
  213. }
  214. static void skip_whitespace(uint8 [] json, ref int index) throws JsonSyntaxError
  215. {
  216. while (index < json.length) {
  217. uint8 c = json[index];
  218. if (c == '/')
  219. skip_comment(json, ref index);
  220. else if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
  221. ++index;
  222. else
  223. break;
  224. }
  225. }
  226. static void skip_comment(uint8 [] json, ref int index) throws JsonSyntaxError
  227. {
  228. uint8 next = json[index + 1];
  229. if (next == '/') {
  230. while (index + 1 < json.length && json[index] != '\n')
  231. ++index;
  232. ++index;
  233. } else if (next == '*') {
  234. while (index + 2 < json.length && (json[index] != '*' || json[index + 1] != '/'))
  235. ++index;
  236. index += 2;
  237. } else {
  238. throw new JsonSyntaxError.BAD_COMMENT("Bad comment");
  239. }
  240. }
  241. static string parse_identifier(uint8 [] json, ref int index) throws JsonSyntaxError
  242. {
  243. skip_whitespace(json, ref index);
  244. if (json[index] == '"')
  245. return parse_string(json, ref index);
  246. Gee.ArrayList<uint8> s = new Gee.ArrayList<uint8>();
  247. while (true) {
  248. uint8 c = json[index];
  249. if (c == ' ' || c == '\t' || c == '\n' || c == '=')
  250. break;
  251. s.add(c);
  252. ++index;
  253. }
  254. s.add('\0');
  255. return (string)s.to_array();
  256. }
  257. static void consume(uint8 [] json, ref int index, string consume) throws JsonSyntaxError
  258. {
  259. skip_whitespace(json, ref index);
  260. for (int i = 0; i < consume.length; ++i) {
  261. if (json[index] != consume[i])
  262. throw new JsonSyntaxError.BAD_TOKEN("Expected '%c' got '%c'".printf(consume[i], json[index]));
  263. ++index;
  264. }
  265. }
  266. static Value? parse_value(uint8 [] json, ref int index) throws JsonSyntaxError
  267. {
  268. uint8 c = next(json, ref index);
  269. if (c == '{') {
  270. return parse_object(json, ref index);
  271. } else if (c == '[') {
  272. return parse_array(json, ref index);
  273. } else if (c == '"') {
  274. return parse_string(json, ref index);
  275. } else if (c == '-' || c >= '0' && c <= '9') {
  276. return parse_number(json, ref index);
  277. } else if (c == 't') {
  278. consume(json, ref index, "true");
  279. return true;
  280. } else if (c == 'f') {
  281. consume(json, ref index, "false");
  282. return false;
  283. } else if (c == 'n') {
  284. consume(json, ref index, "null");
  285. return null;
  286. } else {
  287. throw new JsonSyntaxError.BAD_VALUE("Bad value");
  288. }
  289. }
  290. static uint8 next(uint8 [] json, ref int index) throws JsonSyntaxError
  291. {
  292. skip_whitespace(json, ref index);
  293. return json[index];
  294. }
  295. static Hashtable parse_object(uint8 [] json, ref int index) throws JsonSyntaxError
  296. {
  297. Hashtable ht = new Hashtable();
  298. consume(json, ref index, "{");
  299. skip_whitespace(json, ref index);
  300. while (next(json, ref index) != '}') {
  301. string key = parse_identifier(json, ref index);
  302. consume(json, ref index, "=");
  303. Value? value = parse_value(json, ref index);
  304. ht[key] = value;
  305. }
  306. consume(json, ref index, "}");
  307. return ht;
  308. }
  309. static Gee.ArrayList<Value?> parse_array(uint8 [] json, ref int index) throws JsonSyntaxError
  310. {
  311. Gee.ArrayList<Value?> a = new Gee.ArrayList<Value?>();
  312. consume(json, ref index, "[");
  313. while (next(json, ref index) != ']') {
  314. Value? value = parse_value(json, ref index);
  315. a.add(value);
  316. }
  317. consume(json, ref index, "]");
  318. return a;
  319. }
  320. static string parse_string(uint8[] json, ref int index) throws JsonSyntaxError
  321. {
  322. Gee.ArrayList<uint8> s = new Gee.ArrayList<uint8>();
  323. consume(json, ref index, "\"");
  324. while (true) {
  325. uint8 c = json[index];
  326. ++index;
  327. if (c == '"') {
  328. break;
  329. } else if (c != '\\') {
  330. s.add(c);
  331. } else {
  332. uint8 q = json[index];
  333. ++index;
  334. if (q == '"' || q == '\\' || q == '/')
  335. s.add(q);
  336. else if (q == 'b')
  337. s.add('\b');
  338. else if (q == 'f')
  339. s.add('\f');
  340. else if (q == 'n')
  341. s.add('\n');
  342. else if (q == 'r')
  343. s.add('\r');
  344. else if (q == 't')
  345. s.add('\t');
  346. else if (q == 'u')
  347. throw new JsonSyntaxError.BAD_STRING("Unsupported escape character 'u'");
  348. else
  349. throw new JsonSyntaxError.BAD_STRING("Bad string");
  350. }
  351. }
  352. s.add('\0');
  353. return (string)s.to_array();
  354. }
  355. static double parse_number(uint8[] json, ref int index)
  356. {
  357. int end = index;
  358. while (end < json.length && "0123456789+-.eE".index_of_char((char)json[end]) != -1)
  359. ++end;
  360. uint8[] num = json[index : end];
  361. num += '\0';
  362. index = end;
  363. return double.parse((string)num);
  364. }
  365. }
  366. } /* namespace Crown */