Xml.cpp 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. #define XML_NODE_DATA_SUB_NODE 1 // if keep 'XmlNode.data' as a sub-node when converting it to 'TextNode'
  5. /******************************************************************************/
  6. // TEXT DATA
  7. /******************************************************************************
  8. TextData stores data in C++ like syntax:
  9. group=
  10. {
  11. name=value
  12. array=[0 1 2] // the same as below
  13. array=
  14. {
  15. ``=0
  16. ``=1
  17. ``=2
  18. }
  19. }
  20. simple strings are stored without quotes:
  21. simple
  22. complex strings are stored with quotes:
  23. `complex string`
  24. special characters are encoded by preceeding them with a tilde:
  25. `First Line~nSecond Line`
  26. special characters are: '\0', '\n', '`', '~'
  27. TextNode can have either 1 param 'value' or multiple 'nodes' (sub values)
  28. it cannot have 'value' and 'nodes' set at the same time (if it does then only 'nodes' will be saved)
  29. /******************************************************************************
  30. JSON:
  31. Example #1
  32. {
  33. "name":"value",
  34. "name2":"value2"
  35. }
  36. Example #2
  37. object= // here '=' can occur
  38. {
  39. "name":"value",
  40. "group":{"a":"a_val", "b":"b_val"},
  41. "array":["a", "b"]
  42. }
  43. /******************************************************************************/
  44. // DO NOT CHANGE !!
  45. static const Char8 CodeTextArray[]={'!', '@', '#', '$', '%', '^', '*', '(', ')', '[', ']', '{', '}', '|', '/', '_', '-', '+', '=', ';', ':', ',', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
  46. static const Byte CodeTextIndex[]={255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 2, 3, 4, 255, 255, 7, 8, 6, 17, 21, 16, 22, 14, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 20, 19, 255, 18, 255, 255, 1, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 9, 255, 10, 5, 15, 255, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 11, 13, 12, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; ASSERT(Elms(CodeTextIndex)==256);
  47. static constexpr Int CodeTextElms =Elms(CodeTextArray);
  48. /******************************************************************************
  49. Binary Text Encoding is similar to Ascii85
  50. It Encodes:
  51. 1 byte -> 2 chars (or 1 char depending on byte value)
  52. 2 bytes -> 3 chars
  53. 3 bytes -> 4 chars
  54. 4 bytes -> 5 chars
  55. /******************************************************************************
  56. void MakeCodeTextArray()
  57. {
  58. Str s;
  59. //s+="!@#$%^&*()[]{}<>\\|/_-+=;:,. ~`'\"?"; all
  60. s+="!@#$%^*()[]{}|/_-+=;:,."; // removed: space, &'"<> because they're used in Xml, \ because it's used in JSON, 3 more characters had to be removed, so: ~` because of TextData and ?
  61. for(Int i='0'; i<='9'; i++)s+=Char8(i);
  62. for(Int i='a'; i<='z'; i++)s+=Char8(i);
  63. for(Int i='A'; i<='Z'; i++)s+=Char8(i);
  64. Memc<Char8> cs; FREPA(s)cs.include(s[i]);
  65. DEBUG_ASSERT(cs.elms()==85, S+"85 was the smallest value to represent UInt using 5 characters ("+cs.elms()+')');
  66. FREPA(cs)DEBUG_ASSERT(XmlString(cs[i])==cs[i], "test in case this is stored in xml");
  67. s.clear(); FREPA(cs){if(s.is())s+=", "; s+='\''; if(cs[i]=='\'' || cs[i]=='\\')s+='\\'; s+=cs[i]; s+='\'';}
  68. s.line();
  69. for(Int i=0; i<256; i++){if(s.is() && s.last()!='\n')s+=", "; s+=Byte(cs.find(Char8(i)));} // use Byte cast so -1 -> 255
  70. s.line();
  71. ClipSet(s);
  72. }
  73. void Test()
  74. {
  75. REPA(CodeTextArray)DEBUG_ASSERT(CodeTextIndex[(Byte)CodeTextArray[i]]==i, "err");
  76. }
  77. /******************************************************************************/
  78. #if 0
  79. /******************************************************************************/
  80. Str8 EncodeText( CPtr src , Int size); // encode 'src' binary data of 'size' size, into human readable text
  81. Bool DecodeText(C Str &src, Ptr dest, Int size); // decode binary data from human readable text, false on fail
  82. Str8 EncodeText(CPtr src, Int size)
  83. {
  84. Str8 dest;
  85. if(Byte *s=(Byte*)src)if(size>0)
  86. {
  87. #if 0 // 64-bit version, didn't make a reduction for string length, so use 32-bit version (warning: enabling this would break compatibility with existing 32-bit encoded strings, and would require making decoder 64-bit as well, which is currently not done)
  88. ULong u, max;
  89. REP(UInt(size)/8)
  90. {
  91. for(u=*(ULong*)s, max=ULONG_MAX; ; ){dest+=CodeTextArray[u%CodeTextElms]; max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  92. s+=8;
  93. }
  94. switch(size&7)
  95. {
  96. case 1: u=* s ; max=0x000000000000FF; break;
  97. case 2: u=*(U16*)s ; max=0x0000000000FFFF; break;
  98. case 3: u=*(U16*)s|(s[2]<<16) ; max=0x00000000FFFFFF; break;
  99. case 4: u=*(U32*)s ; max=0x000000FFFFFFFF; break;
  100. case 5: u=*(U32*)s|(ULong( s[4] )<<32); max=0x0000FFFFFFFFFF; break;
  101. case 6: u=*(U32*)s|(ULong(*(U16*)(s+4) )<<32); max=0x00FFFFFFFFFFFF; break;
  102. case 7: u=*(U32*)s|(ULong(*(U16*)(s+4)|(s[6]<<16))<<32); max=0xFFFFFFFFFFFFFF; break;
  103. default: goto end;
  104. }
  105. #else
  106. UInt u, max;
  107. REP(UInt(size)/4)
  108. {
  109. for(u=*(UInt*)s, max=UINT_MAX; ; ){dest+=CodeTextArray[u%CodeTextElms]; max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  110. s+=4;
  111. }
  112. switch(size&3)
  113. {
  114. case 1: u=* s ; max=0x0000FF; break;
  115. case 2: u=*(U16*)s ; max=0x00FFFF; break;
  116. case 3: u=*(U16*)s|(s[2]<<16); max=0xFFFFFF; break;
  117. default: goto end;
  118. }
  119. #endif
  120. for(;;){dest+=CodeTextArray[u%CodeTextElms]; max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  121. end:;
  122. }
  123. return dest;
  124. }
  125. Bool DecodeText(C Str &src, Ptr dest, Int size)
  126. {
  127. if(Byte *d=(Byte*)dest)if(size>0)
  128. {
  129. UInt src_pos=0, u, max, mul;
  130. for(; size>=4; )
  131. {
  132. u=0; max=UINT_MAX; mul=1; for(;;)
  133. {
  134. U16 c= src[src_pos++]; if(!InRange(c, CodeTextIndex)){invalid_char: *(UInt*)d=u; d+=4; size-=4; REP(size)*d++=0; return false;}
  135. Byte i=CodeTextIndex[c]; if(!InRange(i, CodeTextElms ))goto invalid_char;
  136. u+=i*mul; max/=CodeTextElms; if(!max)break; mul*=CodeTextElms;
  137. }
  138. *(UInt*)d=u; d+=4; size-=4;
  139. }
  140. Bool error=false; switch(size)
  141. {
  142. case 1: max=0x0000FF; break;
  143. case 2: max=0x00FFFF; break;
  144. case 3: max=0xFFFFFF; break;
  145. default: goto end;
  146. }
  147. u=0; mul=1; for(;;)
  148. {
  149. U16 c= src[src_pos++]; if(!InRange(c, CodeTextIndex)){error=true; break;}
  150. Byte i=CodeTextIndex[c]; if(!InRange(i, CodeTextElms )){error=true; break;}
  151. u+=i*mul; max/=CodeTextElms; if(!max)break; mul*=CodeTextElms;
  152. }
  153. switch(size)
  154. {
  155. case 1: * d=u; break;
  156. case 2: *(U16*)d=u; break;
  157. case 3: *(U16*)d=u; d[2]=(u>>16); break;
  158. }
  159. end:;
  160. if(error || src.length()!=src_pos)return false; // if there are still characters left then it means that the string is bigger than expected, leave contents on fail
  161. }
  162. return true;
  163. }
  164. /******************************************************************************/
  165. void EncodeText (CPtr src, Int size, Char *dest, Int dest_elms); // encode 'src' binary data of 'size' size, into 'dest' of 'dest_elms' as human readable text
  166. Bool DecodeTextReal(CChar* &src, Dbl &real);
  167. void EncodeText(CPtr src, Int size, Char *dest, Int dest_elms)
  168. {
  169. if(dest && dest_elms>0)
  170. {
  171. dest_elms--; // make room for '\0'
  172. if(dest_elms)if(Byte *s=(Byte*)src)if(size>0)
  173. {
  174. UInt u, max;
  175. REP(UInt(size)/4)
  176. {
  177. for(u=*(UInt*)s, max=UINT_MAX; ; ){*dest++=CodeTextArray[u%CodeTextElms]; if(!--dest_elms)goto end; max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  178. s+=4;
  179. }
  180. switch(size&3)
  181. {
  182. case 1: u=* s ; max=0x0000FF; break;
  183. case 2: u=*(U16*)s ; max=0x00FFFF; break;
  184. case 3: u=*(U16*)s|(s[2]<<16); max=0xFFFFFF; break;
  185. default: goto end;
  186. }
  187. for(;;){*dest++=CodeTextArray[u%CodeTextElms]; if(!--dest_elms)goto end; max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  188. end:;
  189. }
  190. *dest='\0';
  191. }
  192. }
  193. Bool DecodeTextReal(CChar* &src, Dbl &real)
  194. {
  195. UInt u=0, max=UINT_MAX, mul=1; for(;;)
  196. {
  197. U16 c=*src ; if(!InRange(c, CodeTextIndex))return false;
  198. Byte i=CodeTextIndex[c]; if(!InRange(i, CodeTextElms ))return false;
  199. src++; u+=i*mul; max/=CodeTextElms; if(!max)break; mul*=CodeTextElms;
  200. }
  201. U16 c=*src ; if(!InRange(c, CodeTextIndex)){as_flt: real=(Flt&)u; return true;}
  202. Byte i=CodeTextIndex[c]; if(!InRange(i, CodeTextElms ))goto as_flt;
  203. (UInt&)real=u;
  204. u=i; max=UINT_MAX/CodeTextElms; mul=CodeTextElms; src++; for(; max>0; ) // already processed one char
  205. {
  206. U16 c=*src ; if(!InRange(c, CodeTextIndex))return false;
  207. Byte i=CodeTextIndex[c]; if(!InRange(i, CodeTextElms ))return false;
  208. src++; u+=i*mul; max/=CodeTextElms; mul*=CodeTextElms;
  209. }
  210. ((UInt*)&real)[1]=u;
  211. return true;
  212. }
  213. #endif
  214. /******************************************************************************/
  215. #define ERROR '\1' // avoid '\0' because that one means end of file and success
  216. #define QUOTE_BEGIN '`'
  217. #define QUOTE_END '`'
  218. #define BINARY_BEGIN '<'
  219. #define BINARY_END '>'
  220. #define BINARY_ZERO '?' // optimization to store UInt(0) as only one character
  221. #define BINARY_TRIM 0 // because binary data comes from Str and will be loaded into Str, then it will always be aligned to 2 bytes (size of wide Char), this will enable optimizations to reduce the binary size, however it works on assumption that this data will be loaded into Str, if in the future that would be changed, then binary data length may not be preserved
  222. enum TEXT_TYPE
  223. {
  224. TEXT_SIMPLE,
  225. TEXT_QUOTE ,
  226. TEXT_BINARY,
  227. };
  228. static Bool SimpleChar(Char c) {return CharType(c)==CHART_CHAR || c=='-' || c=='.';} // allow - and . for storing negative floats without the quotes
  229. static TEXT_TYPE TextType(C Str &t)
  230. {
  231. Bool simple=t.is(); // empty strings aren't simple and require quote
  232. REPA(t) // entire string needs to be checked, if encountered binary then always return it
  233. {
  234. U16 c=t()[i]; // () avoids range check
  235. if(!SimpleChar(c))
  236. {
  237. if(c>=32 && c<127 // ASCII characters (32 is ' ', 127 is DEL)
  238. //|| c=='\0' even though '\0' can be encoded in TEXT_QUOTE, it will always use 2 characters, so prefer TEXT_BINARY, because this character can occur frequently when encoding raw memory of 0.0f value
  239. || c=='\n' || c=='\t')simple=false;else return TEXT_BINARY;
  240. }
  241. }
  242. return simple ? TEXT_SIMPLE : TEXT_QUOTE;
  243. }
  244. static void SaveText(FileText &f, C Str &t)
  245. {
  246. switch(TextType(t))
  247. {
  248. case TEXT_SIMPLE: f.putText(t); break;
  249. case TEXT_QUOTE:
  250. {
  251. f.putChar(QUOTE_BEGIN);
  252. FREPA(t)switch(Char c=t()[i]) // () avoids range check
  253. {
  254. case '\0': f.putChar('~').putChar('0'); break;
  255. case '\n': f.putChar('~').putChar('n'); break; // we can encode new line below normally too, but prefer this version
  256. //case '\t': f.putChar('~').putChar('t'); break; // we can encode tab below normally instead
  257. case '`' : f.putChar('~').putChar('`'); break;
  258. case '~' : f.putChar('~').putChar('~'); break;
  259. default : if(U16(c)>=32 || c=='\t' || c=='\n')f.putChar(c); break; // '\n' here is supported as well, but prefer as "~n"
  260. }
  261. f.putChar(QUOTE_END);
  262. }break;
  263. case TEXT_BINARY:
  264. {
  265. f.putChar(BINARY_BEGIN);
  266. Byte *src =(Byte*)t();
  267. UInt size=t.length()*SIZE(Char), u, max;
  268. #if BINARY_TRIM
  269. if(size>0 && !(t.last()&0xFF00))size--; // since data is binary packed in Str as Char, the last byte in the Str may not actually be needed, so if it's zero, then remove it
  270. #endif
  271. REP(size/4)
  272. {
  273. u=*(UInt*)src;
  274. #ifdef BINARY_ZERO
  275. if(!u)f.putChar(BINARY_ZERO);else
  276. #endif
  277. for(max=UINT_MAX; ; ){f.putChar(CodeTextArray[u%CodeTextElms]); max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  278. src+=4;
  279. }
  280. switch(size&3)
  281. {
  282. case 1: u=* src ; max= u ; break; // for the last byte we can actually write only as much as we need, because this will generate 1 or 2 chars, and in both cases they will generate only 1 byte
  283. case 2: u=*(U16*)src ; max=(BINARY_TRIM ? u : 0x00FFFF); break; // because data will be stored in Str with Char, it will be aligned to 2 bytes, so we can actually write only as much as we need in this case, because if we write 1..3 chars then during loading they will always be loaded as 1 wide Char (2 bytes)
  284. case 3: u=*(U16*)src|(src[2]<<16); max= 0xFFFFFF ; break;
  285. default: goto end;
  286. }
  287. for(;;){f.putChar(CodeTextArray[u%CodeTextElms]); max/=CodeTextElms; if(!max)break; u/=CodeTextElms;}
  288. end:
  289. f.putChar(BINARY_END);
  290. }break;
  291. }
  292. }
  293. static Bool DecodeText(Char *src, Int src_elms, UInt &out)
  294. {
  295. UInt u=0, mul=1;
  296. REP(src_elms)
  297. {
  298. U16 c=*src++ ; if(!InRange(c, CodeTextIndex))return false;
  299. Byte v=CodeTextIndex[c]; if(!InRange(v, CodeTextElms ))return false;
  300. u+=v*mul; mul*=CodeTextElms;
  301. }
  302. out=u; return true;
  303. }
  304. static Char LoadText(FileText &f, Str &t, Char c)
  305. {
  306. switch(c)
  307. {
  308. default: // TEXT_SIMPLE
  309. {
  310. t=c; // we've already read the first character
  311. for(;;)
  312. {
  313. c=f.getChar();
  314. if(SimpleChar(c))t.alwaysAppend(c); // valid name char
  315. else break;
  316. }
  317. }break;
  318. case QUOTE_BEGIN: // TEXT_QUOTE
  319. {
  320. for(;;)
  321. {
  322. c=f.getChar();
  323. if(c==QUOTE_END)break; // end of name
  324. if(c=='~' ) // special char
  325. {
  326. c=f.getChar();
  327. if(c=='0')t.alwaysAppend('\0');else
  328. if(c=='n')t.alwaysAppend('\n');else
  329. if(c=='t')t.alwaysAppend('\t');else // just in case it was written as special char
  330. if(c=='`')t.alwaysAppend('`' );else
  331. if(c=='~')t.alwaysAppend('~' );else
  332. return ERROR; // invalid char
  333. }else
  334. if(U16(c)>=32 || c=='\t' || c=='\n')t.alwaysAppend(c);else // valid char, '\n' here is supported as well, but prefer as "~n"
  335. if(c!='\r')return ERROR; // skip '\r'
  336. }
  337. c=f.getChar(); // read next char after the name, so we're at the same situation as with the "simple name" case
  338. }break;
  339. case BINARY_BEGIN: // TEXT_BINARY
  340. {
  341. Char src[5]; // max chars per chunk is 5
  342. Int src_elms=0;
  343. UInt out;
  344. for(;;)
  345. {
  346. c=f.getChar();
  347. #ifdef BINARY_ZERO
  348. if(c==BINARY_ZERO)
  349. {
  350. if(src_elms!=0)return ERROR; // BINARY_ZERO can occur only at the start of a chunk
  351. t.alwaysAppend(Char(0));
  352. t.alwaysAppend(Char(0));
  353. continue;
  354. }
  355. #endif
  356. if(c==BINARY_END)break; // end of binary data
  357. src[src_elms++]=c; // add to buffer
  358. if( src_elms==Elms(src)) // if buffer full
  359. {
  360. if(DecodeText(src, src_elms, out))
  361. {
  362. t.alwaysAppend(Char(out&0xFFFF));
  363. t.alwaysAppend(Char(out>> 16));
  364. }else return ERROR; // invalid input (this also handles '\0' chars)
  365. src_elms=0; // clear buffer
  366. }
  367. }
  368. if(src_elms)
  369. {
  370. if(DecodeText(src, src_elms, out)) // process leftovers
  371. {
  372. t.alwaysAppend(Char(out&0xFFFF)); // 1..3 chars correspond to 2 bytes
  373. if(src_elms>=4)t.alwaysAppend(Char(out>>16 )); // 4 chars correspond to 3 bytes
  374. }else return ERROR; // invalid input (this also handles '\0' chars)
  375. }
  376. c=f.getChar(); // read next char after the name, so we're at the same situation as with the "simple name" case
  377. }break;
  378. }
  379. return c;
  380. }
  381. /******************************************************************************/
  382. static Bool SimpleCharJSON(Char c) {return CharType(c)==CHART_CHAR || c=='-' || c=='.';} // JSON treats both - and . as simple chars to allow storing numbers - http://json.org/
  383. static Char LoadTextJSON(FileText &f, Str &t, Char c)
  384. {
  385. if(c=='"') // string
  386. {
  387. for(;;)
  388. {
  389. c=f.getChar();
  390. if(c=='\0')break; // end of file
  391. if(c=='"' )break; // end of string
  392. if(c=='\\') // special char
  393. {
  394. c=f.getChar();
  395. if(c=='0' )t.alwaysAppend('\0');else
  396. if(c=='n' )t.alwaysAppend('\n');else
  397. if(c=='t' )t.alwaysAppend('\t');else // just in case
  398. if(c=='"' )t.alwaysAppend('"' );else
  399. if(c=='\\')t.alwaysAppend('\\');else
  400. if(c=='u' || c=='U')
  401. {
  402. Byte a=CharInt(f.getChar());
  403. Byte b=CharInt(f.getChar());
  404. Byte c=CharInt(f.getChar());
  405. Byte d=CharInt(f.getChar());
  406. t.alwaysAppend(Char((a<<12)|(b<<8)|(c<<4)|d));
  407. }else continue; // invalid char, just skip it
  408. }else
  409. if(U16(c)>=32 || c=='\t')t.alwaysAppend(c);else // valid char
  410. return c; // skip '\r', invalid char (return this one)
  411. }
  412. c=f.getChar(); // read next char after the string, so we're at the same situation as with the "simple name" case
  413. }else // simple name
  414. {
  415. t=c; // we've already read the first character
  416. for(;;)
  417. {
  418. c=f.getChar();
  419. if(SimpleCharJSON(c))t.alwaysAppend(c);else // valid name char
  420. if(c!='\r')break; // skip '\r'
  421. }
  422. }
  423. return c;
  424. }
  425. /******************************************************************************/
  426. static Bool YAMLNameStart(Char c) {return c>' ' && c!='-';}
  427. static Bool YAMLName (Char c) {return c>' ' && c!=':';}
  428. static Char LoadYAMLName (FileText &f, Str &t, Char c)
  429. {
  430. t=c; // we've already read the first character
  431. for(;;)
  432. {
  433. c=f.getChar();
  434. if(YAMLName(c))t.alwaysAppend(c);else // valid name char
  435. if(c!='\r')break;
  436. }
  437. return c;
  438. }
  439. static Bool YAMLValueStart(Char c) {return c> ' ';}
  440. static Bool YAMLValue (Char c) {return c>=' ';}
  441. static Char LoadYAMLValue (FileText &f, Str &t, Char c)
  442. {
  443. if(c=='"') // string
  444. {
  445. for(;;)
  446. {
  447. c=f.getChar();
  448. process:
  449. if(c=='\0')break; // end of file
  450. if(c=='"' )break; // end of string
  451. if(c=='\\') // special char
  452. {
  453. c=f.getChar();
  454. if(c=='0' )t.alwaysAppend('\0');else
  455. if(c=='n' )t.alwaysAppend('\n');else
  456. if(c=='t' )t.alwaysAppend('\t');else // just in case
  457. if(c=='"' )t.alwaysAppend('"' );else
  458. if(c=='\\')t.alwaysAppend('\\');else
  459. if(c=='u' || c=='U')
  460. {
  461. Byte a=CharInt(f.getChar());
  462. Byte b=CharInt(f.getChar());
  463. Byte c=CharInt(f.getChar());
  464. Byte d=CharInt(f.getChar());
  465. t.alwaysAppend(Char((a<<12)|(b<<8)|(c<<4)|d));
  466. }else
  467. continue; // invalid char, just skip it
  468. }else
  469. if(U16(c)>=32 || c=='\t')t.alwaysAppend(c);else // valid char
  470. if(c=='\n')
  471. {
  472. t.space(); for(;;){c=f.getChar(); if(c!=' ' && c!='\r')goto process;}
  473. }else
  474. if(c!='\r')return c; // skip '\r', invalid char (return this one)
  475. }
  476. c=f.getChar(); // read next char after the string, so we're at the same situation as with the "simple name" case
  477. }else
  478. if(c=='\'') // string
  479. {
  480. for(;;)
  481. {
  482. c=f.getChar();
  483. process2:
  484. if(c=='\0')break; // end of file
  485. if(c=='\'')
  486. {
  487. c=f.getChar();
  488. if(c=='\'')t.alwaysAppend('\'');else
  489. return c;
  490. }else
  491. /*if(c=='\\') // special char
  492. {
  493. c=f.getChar();
  494. if(c=='0' )t.alwaysAppend('\0');else
  495. if(c=='n' )t.alwaysAppend('\n');else
  496. if(c=='t' )t.alwaysAppend('\t');else // just in case
  497. if(c=='"' )t.alwaysAppend('"' );else
  498. if(c=='\\')t.alwaysAppend('\\');else
  499. if(c=='u' || c=='U')
  500. {
  501. Byte a=CharInt(f.getChar());
  502. Byte b=CharInt(f.getChar());
  503. Byte c=CharInt(f.getChar());
  504. Byte d=CharInt(f.getChar());
  505. t.alwaysAppend(Char((a<<12)|(b<<8)|(c<<4)|d));
  506. }else continue; // invalid char, just skip it
  507. }else*/
  508. if(U16(c)>=32 || c=='\t')t.alwaysAppend(c);else // valid char
  509. if(c=='\n')
  510. {
  511. t.space(); for(;;){c=f.getChar(); if(c!=' ' && c!='\r')goto process2;}
  512. }else
  513. if(c!='\r')return c; // skip '\r', invalid char (return this one)
  514. }
  515. c=f.getChar(); // read next char after the string, so we're at the same situation as with the "simple name" case
  516. }else // simple name
  517. {
  518. t=c; // we've already read the first character
  519. for(;;)
  520. {
  521. c=f.getChar();
  522. if(YAMLValue(c))t.alwaysAppend(c);else // valid name char
  523. if(c!='\r')break;
  524. }
  525. }
  526. return c;
  527. }
  528. static Char LoadYAMLInlineValue(FileText &f, Str &t, Char c, Char end)
  529. {
  530. t=c; // we've already read the first character
  531. for(;;)
  532. {
  533. c=f.getChar();
  534. if(YAMLValue(c) && c!=',' && c!=end)t.alwaysAppend(c);else // valid name char
  535. break;
  536. }
  537. return c;
  538. }
  539. /******************************************************************************/
  540. TextParam& TextParam::setValueHex(Flt value) {T.value=_TextHex(value); return T;}
  541. TextParam& TextParam::setValueHex(Dbl value) {T.value=_TextHex(value); return T;}
  542. #if 0
  543. TextParam& setValuePacked( Flt value); // set value to 'value' in packed text format
  544. TextParam& setValuePacked( Dbl value); // set value to 'value' in packed text format
  545. TextParam& setValuePacked(C Vec2 &value); // set value to 'value' in packed text format
  546. TextParam& setValuePacked(C Vec &value); // set value to 'value' in packed text format
  547. TextParam& setValuePacked(C Vec4 &value); // set value to 'value' in packed text format
  548. TextParam& setPacked(C Str &name, Flt value) {return setName(name).setValuePacked(value);}
  549. TextParam& setPacked(C Str &name, Dbl value) {return setName(name).setValuePacked(value);}
  550. TextParam& setPacked(C Str &name, C Vec2 &value) {return setName(name).setValuePacked(value);}
  551. TextParam& setPacked(C Str &name, C Vec &value) {return setName(name).setValuePacked(value);}
  552. TextParam& setPacked(C Str &name, C Vec4 &value) {return setName(name).setValuePacked(value);}
  553. TextParam& TextParam::setValuePacked( Flt value) {T.value=_TextPacked(value); return T;}
  554. TextParam& TextParam::setValuePacked( Dbl value) {T.value=_TextPacked(value); return T;}
  555. TextParam& TextParam::setValuePacked(C Vec2 &value) {T.value=_TextPacked(value); return T;}
  556. TextParam& TextParam::setValuePacked(C Vec &value) {T.value=_TextPacked(value); return T;}
  557. TextParam& TextParam::setValuePacked(C Vec4 &value) {T.value=_TextPacked(value); return T;}
  558. #endif
  559. /******************************************************************************/
  560. TextNode* FindNode(MemPtr<TextNode> nodes, C Str &name, Int i)
  561. {
  562. if(InRange(i, nodes))FREPAD(n, nodes) // process in order
  563. {
  564. TextNode &node=nodes[n]; if(node.name==name)
  565. {
  566. if(i==0)return &node; i--;
  567. }
  568. }
  569. return null;
  570. }
  571. TextNode& GetNode(MemPtr<TextNode> nodes, C Str &name)
  572. {
  573. if(TextNode *node=FindNode(nodes, name))return *node; TextNode &node=nodes.New(); node.setName(name); return node;
  574. }
  575. TextNode* TextNode::findNode(C Str &name, Int i) {return FindNode(nodes, name, i);}
  576. TextNode* TextData::findNode(C Str &name, Int i) {return FindNode(nodes, name, i);}
  577. TextNode& TextNode::getNode(C Str &name) {return GetNode(nodes, name);}
  578. TextNode& TextData::getNode(C Str &name) {return GetNode(nodes, name);}
  579. TextNode::TextNode(C XmlNode &xml)
  580. {
  581. set(xml.name, S);
  582. nodes.clear().setNum(xml.params.elms() + xml.nodes.elms() + (XML_NODE_DATA_SUB_NODE ? (xml.data.elms()!=0) : xml.data.elms()));
  583. Int n=0;
  584. FREPA(xml.params)SCAST(TextParam, nodes[n++])=xml.params[i];
  585. FREPA(xml.nodes ) nodes[n++] =xml.nodes [i];
  586. #if XML_NODE_DATA_SUB_NODE
  587. if(xml.data.elms())
  588. {
  589. TextNode &data=nodes[n++];
  590. data.nodes.setNum(xml.data.elms());
  591. FREPAO(data.nodes).value=xml.data[i];
  592. }
  593. #else
  594. FREPA(xml.data)nodes[n++].value=xml.data[i];
  595. #endif
  596. }
  597. XmlNode::XmlNode(C TextNode &text)
  598. {
  599. clear().setName(text.name);
  600. Int sub_nodes=0, data_nodes=0, datas=0;
  601. FREPA(text.nodes)
  602. {
  603. C TextNode &src=text.nodes[i];
  604. if(!src.name .is ()){data_nodes++; datas+=src.nodes.elms();}else
  605. if( src.nodes.elms()) sub_nodes++;
  606. }
  607. params.setNum(text.nodes.elms()-sub_nodes-data_nodes);
  608. nodes .setNum( sub_nodes );
  609. #if XML_NODE_DATA_SUB_NODE
  610. data .setNum(datas );
  611. #else
  612. data .setNum( data_nodes);
  613. #endif
  614. Int p=0, n=0, d=0;
  615. FREPA(text.nodes)
  616. {
  617. C TextNode &src=text.nodes[i];
  618. #if XML_NODE_DATA_SUB_NODE
  619. if(!src.name.is())
  620. {
  621. FREPA(src.nodes)data[d++]=src.nodes[i].value;
  622. }else
  623. #else
  624. if(!src.name.is())
  625. {
  626. data[d++]=src.value;
  627. }else
  628. #endif
  629. if(src.nodes.elms())
  630. {
  631. nodes[n++]=src;
  632. }else
  633. {
  634. SCAST(TextParam, params[p++])=src;
  635. }
  636. }
  637. }
  638. TextData::TextData(C XmlData & xml) {nodes.setNum( xml.nodes.elms()); FREPAO(nodes)= xml.nodes[i];}
  639. XmlData:: XmlData(C TextData &text) {nodes.setNum(text.nodes.elms()); FREPAO(nodes)=text.nodes[i];}
  640. /******************************************************************************/
  641. static Bool EmptyNames(C Memc<TextNode> &nodes) // this can ignore checking for children because it's not necessary
  642. {
  643. REPA(nodes)if(nodes[i].name.is())return false;
  644. return true;
  645. }
  646. static Bool HasChildren(C Memc<TextNode> &nodes)
  647. {
  648. REPA(nodes)if(nodes[i].nodes.elms())return true;
  649. return false;
  650. }
  651. /******************************************************************************/
  652. Bool TextNode::save(FileText &f, Bool just_values)C
  653. {
  654. if(!just_values)
  655. {
  656. f.startLine(); SaveText(f, name);
  657. }
  658. if(value.is() || nodes.elms())
  659. {
  660. if(!just_values)f.putChar('=');
  661. if(!nodes.elms())SaveText(f, value);else // just 'value' is present (save this only when there are no nodes, because they have the priority)
  662. if(EmptyNames(nodes)) // store just the values
  663. {
  664. Bool has_children=HasChildren(nodes);
  665. if( has_children)f.endLine().startLine().depth++;else
  666. if( just_values )f.endLine().startLine();
  667. f.putChar('[');
  668. Bool after_elm=false;
  669. FREPA(nodes)
  670. {
  671. C TextNode &node=nodes[i];
  672. if(node.nodes.elms()) // this node has children
  673. {
  674. after_elm=false;
  675. }else
  676. {
  677. if(after_elm)f.putChar(' ');
  678. else {if(has_children)f.endLine().startLine(); after_elm=true;}
  679. }
  680. if(!node.save(f, true))return false;
  681. }
  682. if(has_children){f.endLine(); f.depth--; f.startLine();}
  683. f.putChar(']');
  684. }else
  685. {
  686. f.endLine().startLine().putChar('{').endLine(); f.depth++;
  687. FREPA(nodes)if(!nodes[i].save(f, false))return false;
  688. f.depth--; f.startLine().putChar('}');
  689. }
  690. }else
  691. if(just_values)SaveText(f, S); // we're storing values, so we need to store something
  692. if(!just_values)f.endLine();
  693. return f.ok();
  694. }
  695. /******************************************************************************/
  696. Char TextNode::load(FileText &f, Bool just_values, Char c)
  697. {
  698. if(just_values)goto get_value;
  699. c=LoadText(f, name, c);
  700. for(; WhiteChar(c); c=f.getChar()); // skip white chars after name
  701. if(c=='=') // has value specified
  702. {
  703. for(c=f.getChar(); WhiteChar(c); c=f.getChar()); // skip white chars after '='
  704. get_value:
  705. if(c=='{') // children
  706. {
  707. for(c=f.getChar(); ; )
  708. {
  709. if(SimpleChar(c) || c==QUOTE_BEGIN || c==BINARY_BEGIN)c=nodes.New().load(f, false, c);else
  710. if( WhiteChar(c)){c=f.getChar(); }else
  711. if(c=='}' ){c=f.getChar(); break;}else
  712. {c= ERROR; break;}
  713. }
  714. }else
  715. if(c=='[') // values
  716. {
  717. for(c=f.getChar(); ; )
  718. {
  719. if(SimpleChar(c) || c==QUOTE_BEGIN || c==BINARY_BEGIN || c=='{' || c=='[')c=nodes.New().load(f, true, c);else
  720. if( WhiteChar(c)){c=f.getChar(); }else
  721. if(c==']' ){c=f.getChar(); break;}else
  722. {c= ERROR; break;}
  723. }
  724. }else
  725. if(SimpleChar(c) || c==QUOTE_BEGIN || c==BINARY_BEGIN) // value
  726. c=LoadText(f, value, c);
  727. }
  728. return c;
  729. }
  730. Char TextNode::loadJSON(FileText &f, Bool just_values, Char c)
  731. {
  732. if(just_values)goto get_value;
  733. c=LoadTextJSON(f, name, c);
  734. for(; WhiteChar(c); c=f.getChar()); // skip white chars after name
  735. if(c==':' || c=='=') // has value specified
  736. {
  737. for(c=f.getChar(); WhiteChar(c); c=f.getChar()); // skip white chars after ':'
  738. get_value:;
  739. if(c=='{') // children
  740. {
  741. for(c=f.getChar(); c; )
  742. {
  743. if(c=='}'){c=' '; break;} // eat this char (replace it with white char, but not '\0' because that's end of file)
  744. if(SimpleCharJSON(c) || c=='"')c=nodes.New().loadJSON(f, false, c);else c=f.getChar();
  745. }
  746. }else
  747. if(c=='[') // values
  748. {
  749. for(c=f.getChar(); c; )
  750. {
  751. if(c==']'){c=' '; break;} // eat this char (replace it with white char, but not '\0' because that's end of file)
  752. if(SimpleCharJSON(c) || c=='"' || c=='{' || c=='[')c=nodes.New().loadJSON(f, true, c);else c=f.getChar();
  753. }
  754. }else
  755. if(SimpleCharJSON(c) || c=='"') // value
  756. c=LoadTextJSON(f, value, c);
  757. }
  758. return c;
  759. }
  760. Char TextNode::loadYAML(FileText &f, Bool just_values, Char c, const Int node_spaces, Int &cur_spaces)
  761. {
  762. if(just_values)goto just_values;
  763. c=LoadYAMLName(f, name, c);
  764. for(; c==' ' || c=='\r'; c=f.getChar()); // skip spaces after name
  765. if(c==':') // has value specified
  766. {
  767. for(c=f.getChar(); c==' ' || c=='\r'; c=f.getChar()); // skip spaces after ':'
  768. if(c=='{') // inline children
  769. for(;;)
  770. {
  771. c=f.getChar();
  772. process_a:
  773. if(c==' ' || c=='\n' || c=='\r'){}else
  774. if(c==',' )nodes.New();else
  775. if(c=='}' ){c=f.getChar(); break;}else
  776. if(YAMLNameStart(c))
  777. {
  778. TextNode &child=nodes.New();
  779. c=LoadYAMLName(f, child.name, c);
  780. for(; c==' ' || c=='\r'; c=f.getChar()); // skip spaces after name
  781. if(c==':') // has value specified
  782. {
  783. for(c=f.getChar(); c==' ' || c=='\r'; c=f.getChar()); // skip spaces after ':'
  784. if(YAMLValueStart(c))c=LoadYAMLInlineValue(f, child.value, c, '}');
  785. }
  786. if(c==',')c=' '; // eat this
  787. goto process_a;
  788. }else
  789. return '\0'; // fail
  790. }else
  791. if(c=='[') // inline values
  792. for(;;)
  793. {
  794. c=f.getChar();
  795. process_b:
  796. if(c==' ' || c=='\n' || c=='\r'){}else
  797. if(c==',')nodes.New();else
  798. if(c==']'){c=f.getChar(); break;}else
  799. if(YAMLValueStart(c))
  800. {
  801. c=LoadYAMLInlineValue(f, nodes.New().value, c, ']'); if(c==',')c=f.getChar();
  802. goto process_b;
  803. }else
  804. return '\0'; // fail
  805. }else
  806. if(YAMLValueStart(c)) // value
  807. c=LoadYAMLValue(f, value, c);
  808. else
  809. if(c=='\n') // potential children nodes, or end of this node
  810. {
  811. cur_spaces=0;
  812. just_values:
  813. for(c=f.getChar(); ; )
  814. {
  815. if(c==' ' ){cur_spaces++; c=f.getChar();}else
  816. if(c=='\n'){cur_spaces=0; c=f.getChar();}else
  817. if(c=='#' ){cur_spaces=0; f.skipLine(); c=f.getChar();}else // comment
  818. if(c=='\r'){ c=f.getChar();}else
  819. if(YAMLNameStart(c))
  820. {
  821. if(cur_spaces>node_spaces)
  822. {
  823. c=nodes.New().loadYAML(f, false, c, cur_spaces, cur_spaces);
  824. }else return c;
  825. }else
  826. if(c=='-')
  827. {
  828. if(cur_spaces+2>node_spaces && !just_values) // "!just_values" means that we're not going to add another list element to existing nameless element, instead we need to return to parent, and create another nameless element
  829. {
  830. cur_spaces+=2;
  831. c=f.getChar(); if(c==' ')c=nodes.New().loadYAML(f, true, c, cur_spaces-2, cur_spaces);else return '\0';
  832. }else return c;
  833. }else
  834. break;
  835. }
  836. }
  837. }
  838. return c;
  839. }
  840. /******************************************************************************/
  841. Bool TextData::save(FileText &f)C
  842. {
  843. FREPA(nodes)if(!nodes[i].save(f, false))return false;
  844. return f.ok();
  845. }
  846. Bool TextData::save(C Str &name, ENCODING encoding, INDENT indent, Cipher *cipher)C
  847. {
  848. FileText f; if(f.write(name, encoding, cipher)){f.indent=indent; if(save(f) && f.flush())return true; /*f.del(); FDelFile(name);*/} // no need to delete incomplete text files, because they can be still readable, just partially
  849. return false;
  850. }
  851. Bool TextData::load(FileText &f)
  852. {
  853. clear();
  854. for(Char c=f.getChar(); ; )
  855. {
  856. if(SimpleChar(c) || c==QUOTE_BEGIN || c==BINARY_BEGIN)c=nodes.New().load(f, false, c);else
  857. if( WhiteChar(c))c=f.getChar();else
  858. if(!c)return true ;else // don't check for 'f.ok' because methods stop on null char and not on 'f.end'
  859. return false;
  860. }
  861. }
  862. Bool TextData::loadJSON(FileText &f)
  863. {
  864. clear();
  865. for(Char c=f.getChar(); ; )
  866. {
  867. if(SimpleCharJSON(c) || c=='"' || c=='{' || c=='[')c=nodes.New().loadJSON(f, c=='{' || c=='[', c);else
  868. if( WhiteChar(c))c=f.getChar();else
  869. if(!c)return true ;else // don't check for 'f.ok' because methods stop on null char and not on 'f.end'
  870. return false;
  871. }
  872. }
  873. Bool TextData::loadYAML(FileText &f)
  874. {
  875. clear();
  876. Int spaces=0;
  877. for(Char c=f.getChar(); ; )
  878. {
  879. if(c==' ' ){spaces++; c=f.getChar();}else
  880. if(c=='\n'){spaces=0; c=f.getChar();}else
  881. if(c=='#' ){spaces=0; f.skipLine(); c=f.getChar();}else // comment
  882. if(c=='-' ){spaces=0; f.skipLine(); c=f.getChar();}else
  883. if(c=='\r'){ c=f.getChar();}else
  884. if(YAMLNameStart(c))c=nodes.New().loadYAML(f, false, c, spaces, spaces);else
  885. if(!c)return true ;else // don't check for 'f.ok' because methods stop on null char and not on 'f.end'
  886. return false;
  887. }
  888. }
  889. Bool TextData::load(C Str &name, Cipher *cipher)
  890. {
  891. FileText f; if(f.read(name, cipher))return load(f);
  892. clear(); return false;
  893. }
  894. Bool TextData::load(C UID &id, Cipher *cipher)
  895. {
  896. FileText f; if(f.read(id, cipher))return load(f);
  897. clear(); return false;
  898. }
  899. Bool TextData::loadJSON(C Str &name, Cipher *cipher)
  900. {
  901. FileText f; if(f.read(name, cipher))return loadJSON(f);
  902. clear(); return false;
  903. }
  904. Bool TextData::loadYAML(C Str &name, Cipher *cipher)
  905. {
  906. FileText f; if(f.read(name, cipher))return loadYAML(f);
  907. clear(); return false;
  908. }
  909. /******************************************************************************/
  910. // XML DATA
  911. /******************************************************************************/
  912. XmlNode* FindNode(MemPtr<XmlNode> nodes, C Str &name, Int i)
  913. {
  914. if(InRange(i, nodes))FREPAD(n, nodes) // process in order
  915. {
  916. XmlNode &node=nodes[n]; if(node.name==name)
  917. {
  918. if(i==0)return &node; i--;
  919. }
  920. }
  921. return null;
  922. }
  923. XmlNode& GetNode(MemPtr<XmlNode> nodes, C Str &name)
  924. {
  925. if(XmlNode *node=FindNode(nodes, name))return *node; return nodes.New().setName(name);
  926. }
  927. XmlParam* XmlNode::findParam(C Str &name, Int i)
  928. {
  929. if(InRange(i, params))FREPAD(n, params) // process in order
  930. {
  931. XmlParam &param=params[n]; if(param.name==name)
  932. {
  933. if(i==0)return &param; i--;
  934. }
  935. }
  936. return null;
  937. }
  938. XmlNode* XmlNode::findNode(C Str &name, Int i) {return FindNode(nodes, name, i);}
  939. XmlNode* XmlData::findNode(C Str &name, Int i) {return FindNode(nodes, name, i);}
  940. XmlParam& XmlNode::getParam(C Str &name) {if(XmlParam *par=findParam(name))return *par; return params.New().setName(name);}
  941. XmlNode & XmlNode::getNode (C Str &name) {return GetNode(nodes, name);}
  942. XmlNode & XmlData::getNode (C Str &name) {return GetNode(nodes, name);}
  943. /******************************************************************************/
  944. static Str XmlParamString(C Str &str)
  945. {
  946. Str temp; temp.reserve(str.length());
  947. FREPA(str)
  948. {
  949. Char c=str()[i]; // () avoids range check
  950. if(c=='&' )temp+="&amp;" ;else
  951. if(c=='<' )temp+="&lt;" ;else
  952. if(c=='>' )temp+="&gt;" ;else
  953. //if(c=='\'')temp+="&apos;";else we always use " to save params, so we can skip encoding ' to reduce size
  954. if(c=='"' )temp+="&quot;";else
  955. temp+=c;
  956. }
  957. return temp;
  958. }
  959. static Str XmlDataString(C Str &str)
  960. {
  961. Str temp; temp.reserve(str.length());
  962. FREPA(str)
  963. {
  964. Char c=str()[i]; // () avoids range check
  965. if(c=='&' )temp+="&amp;" ;else
  966. if(c=='<' )temp+="&lt;" ;else
  967. if(c=='>' )temp+="&gt;" ;else
  968. //if(c=='\'')temp+="&apos;";else this is not required for data so skip to reduce size
  969. //if(c=='"' )temp+="&quot;";else this is not required for data so skip to reduce size
  970. temp+=c;
  971. }
  972. return temp;
  973. }
  974. Bool XmlNode::save(FileText &f, Bool params_in_separate_lines)C
  975. {
  976. Str s=S+'<'+name;
  977. if(params_in_separate_lines)
  978. {
  979. if(params.elms())
  980. {
  981. f.putLine(s); s.clear();
  982. f.depth++;
  983. FREPA(params)f.putLine(params[i].name+"=\""+XmlParamString(params[i].value)+'"');
  984. f.depth--;
  985. }
  986. }else
  987. {
  988. FREPA(params)s+=S+' '+params[i].name+"=\""+XmlParamString(params[i].value)+'"';
  989. }
  990. #if 0 // 'data' in different line
  991. if(data.elms() || nodes.elms())s+='>';else s+="/>"; f.putLine(s);
  992. if(data.elms() || nodes.elms())
  993. {
  994. f.depth++;
  995. if(data.elms()){s.clear(); FREPA(data)s.space()+=XmlDataString(data[i]); f.putLine(s);}
  996. FREPA(nodes)if(!nodes[i].save(f, params_in_separate_lines))return false;
  997. f.depth--;
  998. f.putLine(S+"</"+name+'>');
  999. }
  1000. #else // MS Visual Studio 2010 xml parser breaks if 'data' is in different line, so put data in the same line:
  1001. if(!data.elms() && !nodes.elms())f.putLine(s+="/>");else
  1002. {
  1003. s+='>';
  1004. FREPA(data)
  1005. {
  1006. if(i)s+=' ';
  1007. s+=XmlDataString(data[i]);
  1008. }
  1009. if(!nodes.elms())f.putLine(s+=S+"</"+name+'>');else
  1010. {
  1011. f.putLine(s);
  1012. f.depth++; FREPA(nodes)if(!nodes[i].save(f, params_in_separate_lines))return false;
  1013. f.depth--;
  1014. f.putLine(S+"</"+name+'>');
  1015. }
  1016. }
  1017. #endif
  1018. return f.ok();
  1019. }
  1020. Bool XmlData::save(FileText &f, Bool params_in_separate_lines)C
  1021. {
  1022. FREPA(nodes)if(!nodes[i].save(f, params_in_separate_lines))return false;
  1023. return f.ok();
  1024. }
  1025. Bool XmlData::save(C Str &name, Bool params_in_separate_lines, ENCODING encoding)C
  1026. {
  1027. FileText f; if(f.write(name, encoding)){if(save(f, params_in_separate_lines) && f.flush())return true; /*f.del(); FDelFile(name);*/} // no need to delete incomplete text files, because they can be still readable, just partially
  1028. return false;
  1029. }
  1030. /******************************************************************************/
  1031. static Bool XmlChar(Char c) {return CharType(c)==CHART_CHAR || c=='-' || c==':' || c=='.';} // '-' is a valid char for xml node names, ':' is a valid char for xml param names ("AndroidManifest.xml" uses those), '.' is a valid char for xml param names, http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
  1032. static Char LoadXmlName(FileText &f, Str &name, Char c)
  1033. {
  1034. for(; XmlChar(c); c=f.getChar())name+=c;
  1035. return c;
  1036. }
  1037. static Bool LoadXmlValue(FileText &f, Str &value)
  1038. {
  1039. Char q=f.getChar();
  1040. for(; WhiteChar(q); q=f.getChar()); // skip white chars
  1041. if(q!='"' && q!='\'')return false; // value (both " and ' are valid in XML Attributes - https://www.w3schools.com/xml/xml_attributes.asp)
  1042. for(;;)
  1043. {
  1044. Char c=f.getChar(); if(!c)return false; // eof
  1045. if(c==q) // end of value
  1046. {
  1047. value=DecodeXmlString(value);
  1048. return true;
  1049. }
  1050. if(U16(c)>=32 || c=='\t')value+=c;
  1051. }
  1052. }
  1053. static Char LoadXmlData(FileText &f, Str &data, Char c)
  1054. {
  1055. data=c;
  1056. for(;;)
  1057. {
  1058. c=f.getChar(); if(!c || WhiteChar(c) || c=='<')break;
  1059. if(U16(c)>=32 || c=='\t' || c=='\n')data+=c;
  1060. }
  1061. data=DecodeXmlString(data);
  1062. return c;
  1063. }
  1064. /******************************************************************************/
  1065. Bool XmlNode::load(FileText &f, Char first_char)
  1066. {
  1067. // node name
  1068. Char c=LoadXmlName(f, name, first_char);
  1069. // node params
  1070. for(;;)
  1071. {
  1072. if(WhiteChar(c))c=f.getChar();else // skip white char
  1073. if(XmlChar(c)) // param
  1074. {
  1075. XmlParam &param=params.New();
  1076. c=LoadXmlName(f, param.name, c); // load param name
  1077. for(; WhiteChar(c); c=f.getChar()); // skip white chars
  1078. if(c=='='){if(!LoadXmlValue(f, param.value))return false; c=' ';} // param value
  1079. }else break;
  1080. }
  1081. // node end
  1082. if(c=='/')return f.getChar()=='>';
  1083. if(c!='>')return false;
  1084. // node body
  1085. for(;;)
  1086. {
  1087. Char c=f.getChar();
  1088. next:;
  1089. if( !c)return false; // unexpected end
  1090. if( c=='<') // <node ..>, </node>, <?xml version="1.0" encoding="UTF-8" ?>, <!-- Comment --> or <!Data .. >
  1091. {
  1092. c=f.getChar();
  1093. if(c=='/' || c=='?')
  1094. {
  1095. for(;;)
  1096. {
  1097. Char t=f.getChar();
  1098. if( !t )return false; // unexpected end
  1099. if( t=='>')break; // tag end
  1100. }
  1101. if(c=='/')return true; // if this was a node end
  1102. }else
  1103. if(c=='!')
  1104. {
  1105. Bool comment=false;
  1106. c=f.getChar();
  1107. if(c=='-'){c=f.getChar(); if(c=='-'){c=f.getChar(); comment=true;}}
  1108. for(Int dashes=0; ; )
  1109. {
  1110. if(!c )return false; // unexpected end
  1111. if( c=='>' && (!comment || dashes>=2))break; // tag end
  1112. if( c=='-' )dashes++;else dashes=0;
  1113. c=f.getChar();
  1114. }
  1115. }else
  1116. if(!nodes.New().load(f, c))return false;
  1117. }else
  1118. if(!WhiteChar(c)){c=LoadXmlData(f, data.New(), c); goto next;}
  1119. }
  1120. return true; // don't check for 'f.ok' because methods stop on null char and not on 'f.end'
  1121. }
  1122. /******************************************************************************/
  1123. Bool XmlData::load(FileText &f) // if file is incomplete then false is returned and partial data is available
  1124. {
  1125. clear();
  1126. for(; !f.end(); )
  1127. {
  1128. Char c=f.getChar();
  1129. if(c=='<') // <node ..>, <?xml version="1.0" encoding="UTF-8" ?>, <!-- Comment --> or <!Data .. >
  1130. {
  1131. c=f.getChar();
  1132. if(c=='?')
  1133. {
  1134. for(;;)
  1135. {
  1136. c=f.getChar();
  1137. if(!c )return false; // unexpected end
  1138. if( c=='>')break; // tag end
  1139. }
  1140. }else
  1141. if(c=='!')
  1142. {
  1143. Bool comment=false;
  1144. c=f.getChar();
  1145. if(c=='-'){c=f.getChar(); if(c=='-'){c=f.getChar(); comment=true;}}
  1146. for(Int dashes=0; ; )
  1147. {
  1148. if(!c )return false; // unexpected end
  1149. if( c=='>' && (!comment || dashes>=2))break; // tag end
  1150. if( c=='-' )dashes++;else dashes=0;
  1151. c=f.getChar();
  1152. }
  1153. }else
  1154. if(!nodes.New().load(f, c))return false;
  1155. }else if(!WhiteChar(c))return false;
  1156. }
  1157. return true; // don't check for 'f.ok' because methods stop on null char and not on 'f.end'
  1158. }
  1159. Bool XmlData::load(C Str &name)
  1160. {
  1161. FileText f; if(f.read(name))return load(f);
  1162. clear(); return false;
  1163. }
  1164. Bool XmlData::load(C UID &id)
  1165. {
  1166. FileText f; if(f.read(id))return load(f);
  1167. clear(); return false;
  1168. }
  1169. /******************************************************************************/
  1170. }
  1171. /******************************************************************************/