sjson.vala 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. * Copyright (c) 2012-2024 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)
  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)
  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)
  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)
  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)
  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)
  78. {
  79. FileStream fs = FileStream.open(path, "wb");
  80. if (fs == null)
  81. return;
  82. fs.write(encode(h).data);
  83. }
  84. static void write_root_object(Hashtable t, StringBuilder builder)
  85. {
  86. write_object_fields(t, builder, 0);
  87. }
  88. static void write_object_fields(Hashtable t, StringBuilder builder, int indentation)
  89. {
  90. Gee.ArrayList<string> keys = new Gee.ArrayList<string>.wrap(t.keys.to_array());
  91. keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
  92. foreach (string key in keys) {
  93. write_new_line(builder, indentation);
  94. builder.append(key);
  95. builder.append(" = ");
  96. write(t[key], builder, indentation);
  97. }
  98. }
  99. static void write_new_line(StringBuilder builder, int indentation)
  100. {
  101. if (builder.len > 0)
  102. builder.append_c('\n');
  103. for (int i = 0; i < indentation; ++i)
  104. builder.append_c('\t');
  105. }
  106. static void write(Value? o, StringBuilder builder, int indentation)
  107. {
  108. if (o == null)
  109. builder.append("null");
  110. else if (o.holds(typeof(bool)) && (bool)o == false)
  111. builder.append("false");
  112. else if (o.holds(typeof(bool)))
  113. builder.append("true");
  114. else if (o.holds(typeof(uint8)))
  115. builder.append_printf("%u", (uint8)o);
  116. else if (o.holds(typeof(int)))
  117. builder.append_printf("%d", (int)o);
  118. else if (o.holds(typeof(float)))
  119. builder.append_printf("%.9g", (float)o);
  120. else if (o.holds(typeof(double)))
  121. builder.append_printf("%.17g", (double)o);
  122. else if (o.holds(typeof(string)))
  123. write_string((string)o, builder);
  124. else if (o.holds(typeof(Gee.ArrayList)))
  125. write_array((Gee.ArrayList)o, builder, indentation);
  126. else if (o.holds(typeof(Hashtable)))
  127. write_object((Hashtable)o, builder, indentation);
  128. else
  129. GLib.assert(false);
  130. }
  131. static void write_string(string s, StringBuilder builder)
  132. {
  133. builder.append_c('"');
  134. for (int i = 0; i < s.length; ++i) {
  135. char c = s[i];
  136. if (c == '"' || c == '\\')
  137. builder.append_c('\\');
  138. builder.append_c(c);
  139. }
  140. builder.append_c('"');
  141. }
  142. static void write_array(Gee.ArrayList<Value?> a, StringBuilder builder, int indentation)
  143. {
  144. Gee.ArrayList<Value?> a_sorted = a;
  145. a_sorted.sort((a, b) => {
  146. if (!a.holds(typeof(Hashtable)) || !b.holds(typeof(Hashtable)))
  147. return 0;
  148. Hashtable obj_a = a as Hashtable;
  149. Hashtable obj_b = b as Hashtable;
  150. string guid_a_str;
  151. string guid_b_str;
  152. if (obj_a.has_key("id")) {
  153. Value? val = obj_a["id"];
  154. if (val.holds(typeof(string)))
  155. guid_a_str = (string)val;
  156. else
  157. // The 'id' key has been used for something else than a Guid. Font files are
  158. // an example of the 'id' key used to store the codepoint of a glyph.
  159. return 0;
  160. } else if (obj_a.has_key("_guid")) {
  161. guid_a_str = (string)obj_a["_guid"];
  162. } else {
  163. return 0;
  164. }
  165. if (obj_b.has_key("id")) {
  166. Value? val = obj_b["id"];
  167. if (val.holds(typeof(string)))
  168. guid_b_str = (string)val;
  169. else
  170. return 0; // See comment above.
  171. } else if (obj_b.has_key("_guid")) {
  172. guid_b_str = (string)obj_b["_guid"];
  173. } else {
  174. return 0;
  175. }
  176. Guid guid_a = Guid.parse(guid_a_str);
  177. Guid guid_b = Guid.parse(guid_b_str);
  178. return Guid.compare_func(guid_a, guid_b);
  179. });
  180. builder.append_c('[');
  181. foreach (Value? item in a_sorted) {
  182. write_new_line(builder, indentation + 1);
  183. write(item, builder, indentation + 1);
  184. }
  185. write_new_line(builder, indentation);
  186. builder.append_c(']');
  187. }
  188. static void write_object(Hashtable t, StringBuilder builder, int indentation)
  189. {
  190. builder.append_c('{');
  191. write_object_fields(t, builder, indentation + 1);
  192. write_new_line(builder, indentation);
  193. builder.append_c('}');
  194. }
  195. static Hashtable parse_root_object(uint8 [] json, ref int index)
  196. {
  197. Hashtable ht = new Hashtable();
  198. while (!at_end(json, ref index)) {
  199. string key = parse_identifier(json, ref index);
  200. consume(json, ref index, "=");
  201. Value? value = parse_value(json, ref index);
  202. ht[key] = value;
  203. }
  204. return ht;
  205. }
  206. static bool at_end(uint8 [] json, ref int index)
  207. {
  208. skip_whitespace(json, ref index);
  209. return index >= json.length;
  210. }
  211. static void skip_whitespace(uint8 [] json, ref int index)
  212. {
  213. while (index < json.length) {
  214. uint8 c = json[index];
  215. if (c == '/')
  216. skip_comment(json, ref index);
  217. else if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
  218. ++index;
  219. else
  220. break;
  221. }
  222. }
  223. static void skip_comment(uint8 [] json, ref int index)
  224. {
  225. uint8 next = json[index + 1];
  226. if (next == '/') {
  227. while (index + 1 < json.length && json[index] != '\n')
  228. ++index;
  229. ++index;
  230. } else if (next == '*') {
  231. while (index + 2 < json.length && (json[index] != '*' || json[index + 1] != '/'))
  232. ++index;
  233. index += 2;
  234. } else {
  235. GLib.assert(false);
  236. }
  237. }
  238. static string parse_identifier(uint8 [] json, ref int index)
  239. {
  240. skip_whitespace(json, ref index);
  241. if (json[index] == '"')
  242. return parse_string(json, ref index);
  243. Gee.ArrayList<uint8> s = new Gee.ArrayList<uint8>();
  244. while (true) {
  245. uint8 c = json[index];
  246. if (c == ' ' || c == '\t' || c == '\n' || c == '=')
  247. break;
  248. s.add(c);
  249. ++index;
  250. }
  251. s.add('\0');
  252. return (string)s.to_array();
  253. }
  254. static void consume(uint8 [] json, ref int index, string consume)
  255. {
  256. skip_whitespace(json, ref index);
  257. for (int i = 0; i < consume.length; ++i) {
  258. if (json[index] != consume[i])
  259. GLib.assert(false);
  260. ++index;
  261. }
  262. }
  263. static Value? parse_value(uint8 [] json, ref int index)
  264. {
  265. uint8 c = next(json, ref index);
  266. if (c == '{') {
  267. return parse_object(json, ref index);
  268. } else if (c == '[') {
  269. return parse_array(json, ref index);
  270. } else if (c == '"') {
  271. return parse_string(json, ref index);
  272. } else if (c == '-' || c >= '0' && c <= '9') {
  273. return parse_number(json, ref index);
  274. } else if (c == 't') {
  275. consume(json, ref index, "true");
  276. return true;
  277. } else if (c == 'f') {
  278. consume(json, ref index, "false");
  279. return false;
  280. } else if (c == 'n') {
  281. consume(json, ref index, "null");
  282. return null;
  283. } else {
  284. GLib.assert(false);
  285. return null;
  286. }
  287. }
  288. static uint8 next(uint8 [] json, ref int index)
  289. {
  290. skip_whitespace(json, ref index);
  291. return json[index];
  292. }
  293. static Hashtable parse_object(uint8 [] json, ref int index)
  294. {
  295. Hashtable ht = new Hashtable();
  296. consume(json, ref index, "{");
  297. skip_whitespace(json, ref index);
  298. while (next(json, ref index) != '}') {
  299. string key = parse_identifier(json, ref index);
  300. consume(json, ref index, "=");
  301. Value? value = parse_value(json, ref index);
  302. ht[key] = value;
  303. }
  304. consume(json, ref index, "}");
  305. return ht;
  306. }
  307. static Gee.ArrayList<Value?> parse_array(uint8 [] json, ref int index)
  308. {
  309. Gee.ArrayList<Value?> a = new Gee.ArrayList<Value?>();
  310. consume(json, ref index, "[");
  311. while (next(json, ref index) != ']') {
  312. Value? value = parse_value(json, ref index);
  313. a.add(value);
  314. }
  315. consume(json, ref index, "]");
  316. return a;
  317. }
  318. static string parse_string(uint8[] json, ref int index)
  319. {
  320. Gee.ArrayList<uint8> s = new Gee.ArrayList<uint8>();
  321. consume(json, ref index, "\"");
  322. while (true) {
  323. uint8 c = json[index];
  324. ++index;
  325. if (c == '"') {
  326. break;
  327. } else if (c != '\\') {
  328. s.add(c);
  329. } else {
  330. uint8 q = json[index];
  331. ++index;
  332. if (q == '"' || q == '\\' || q == '/')
  333. s.add(q);
  334. else if (q == 'b')
  335. s.add('\b');
  336. else if (q == 'f')
  337. s.add('\f');
  338. else if (q == 'n')
  339. s.add('\n');
  340. else if (q == 'r')
  341. s.add('\r');
  342. else if (q == 't')
  343. s.add('\t');
  344. else if (q == 'u')
  345. GLib.assert(false);
  346. else
  347. GLib.assert(false);
  348. }
  349. }
  350. s.add('\0');
  351. return (string)s.to_array();
  352. }
  353. static double parse_number(uint8[] json, ref int index)
  354. {
  355. int end = index;
  356. while (end < json.length && "0123456789+-.eE".index_of_char((char)json[end]) != -1)
  357. ++end;
  358. uint8[] num = json[index : end];
  359. num += '\0';
  360. index = end;
  361. return double.parse((string)num);
  362. }
  363. }
  364. } /* namespace Crown */