Repositorio del curso CCOM4030 el semestre B91 del proyecto kilometro0

bplistCreator.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. 'use strict';
  2. // adapted from http://code.google.com/p/plist/source/browse/trunk/src/main/java/com/dd/plist/BinaryPropertyListWriter.java
  3. var streamBuffers = require("stream-buffers");
  4. var debug = false;
  5. function Real(value) {
  6. this.value = value;
  7. }
  8. module.exports = function(dicts) {
  9. var buffer = new streamBuffers.WritableStreamBuffer();
  10. buffer.write(new Buffer("bplist00"));
  11. if (debug) {
  12. console.log('create', require('util').inspect(dicts, false, 10));
  13. }
  14. if (dicts instanceof Array && dicts.length === 1) {
  15. dicts = dicts[0];
  16. }
  17. var entries = toEntries(dicts);
  18. if (debug) {
  19. console.log('entries', entries);
  20. }
  21. var idSizeInBytes = computeIdSizeInBytes(entries.length);
  22. var offsets = [];
  23. var offsetSizeInBytes;
  24. var offsetTableOffset;
  25. updateEntryIds();
  26. entries.forEach(function(entry, entryIdx) {
  27. offsets[entryIdx] = buffer.size();
  28. if (!entry) {
  29. buffer.write(0x00);
  30. } else {
  31. write(entry);
  32. }
  33. });
  34. writeOffsetTable();
  35. writeTrailer();
  36. return buffer.getContents();
  37. function updateEntryIds() {
  38. var strings = {};
  39. var entryId = 0;
  40. entries.forEach(function(entry) {
  41. if (entry.id) {
  42. return;
  43. }
  44. if (entry.type === 'string') {
  45. if (!entry.bplistOverride && strings.hasOwnProperty(entry.value)) {
  46. entry.type = 'stringref';
  47. entry.id = strings[entry.value];
  48. } else {
  49. strings[entry.value] = entry.id = entryId++;
  50. }
  51. } else {
  52. entry.id = entryId++;
  53. }
  54. });
  55. entries = entries.filter(function(entry) {
  56. return (entry.type !== 'stringref');
  57. });
  58. }
  59. function writeTrailer() {
  60. if (debug) {
  61. console.log('0x' + buffer.size().toString(16), 'writeTrailer');
  62. }
  63. // 6 null bytes
  64. buffer.write(new Buffer([0, 0, 0, 0, 0, 0]));
  65. // size of an offset
  66. if (debug) {
  67. console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', offsetSizeInBytes);
  68. }
  69. writeByte(offsetSizeInBytes);
  70. // size of a ref
  71. if (debug) {
  72. console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', idSizeInBytes);
  73. }
  74. writeByte(idSizeInBytes);
  75. // number of objects
  76. if (debug) {
  77. console.log('0x' + buffer.size().toString(16), 'writeTrailer(number of objects):', entries.length);
  78. }
  79. writeLong(entries.length);
  80. // top object
  81. if (debug) {
  82. console.log('0x' + buffer.size().toString(16), 'writeTrailer(top object)');
  83. }
  84. writeLong(0);
  85. // offset table offset
  86. if (debug) {
  87. console.log('0x' + buffer.size().toString(16), 'writeTrailer(offset table offset):', offsetTableOffset);
  88. }
  89. writeLong(offsetTableOffset);
  90. }
  91. function writeOffsetTable() {
  92. if (debug) {
  93. console.log('0x' + buffer.size().toString(16), 'writeOffsetTable');
  94. }
  95. offsetTableOffset = buffer.size();
  96. offsetSizeInBytes = computeOffsetSizeInBytes(offsetTableOffset);
  97. offsets.forEach(function(offset) {
  98. writeBytes(offset, offsetSizeInBytes);
  99. });
  100. }
  101. function write(entry) {
  102. switch (entry.type) {
  103. case 'dict':
  104. writeDict(entry);
  105. break;
  106. case 'number':
  107. case 'double':
  108. writeNumber(entry);
  109. break;
  110. case 'UID':
  111. writeUID(entry);
  112. break;
  113. case 'array':
  114. writeArray(entry);
  115. break;
  116. case 'boolean':
  117. writeBoolean(entry);
  118. break;
  119. case 'string':
  120. case 'string-utf16':
  121. writeString(entry);
  122. break;
  123. case 'date':
  124. writeDate(entry);
  125. break;
  126. case 'data':
  127. writeData(entry);
  128. break;
  129. default:
  130. throw new Error("unhandled entry type: " + entry.type);
  131. }
  132. }
  133. function writeDate(entry) {
  134. writeByte(0x33);
  135. var date = (Date.parse(entry.value)/1000) - 978307200
  136. writeDouble(date)
  137. }
  138. function writeDict(entry) {
  139. if (debug) {
  140. var keysStr = entry.entryKeys.map(function(k) {return k.id;});
  141. var valsStr = entry.entryValues.map(function(k) {return k.id;});
  142. console.log('0x' + buffer.size().toString(16), 'writeDict', '(id: ' + entry.id + ')', '(keys: ' + keysStr + ')', '(values: ' + valsStr + ')');
  143. }
  144. writeIntHeader(0xD, entry.entryKeys.length);
  145. entry.entryKeys.forEach(function(entry) {
  146. writeID(entry.id);
  147. });
  148. entry.entryValues.forEach(function(entry) {
  149. writeID(entry.id);
  150. });
  151. }
  152. function writeNumber(entry) {
  153. if (debug) {
  154. console.log('0x' + buffer.size().toString(16), 'writeNumber', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')');
  155. }
  156. if (entry.type !== 'double' && parseFloat(entry.value.toFixed()) == entry.value) {
  157. if (entry.value < 0) {
  158. writeByte(0x13);
  159. writeBytes(entry.value, 8);
  160. } else if (entry.value <= 0xff) {
  161. writeByte(0x10);
  162. writeBytes(entry.value, 1);
  163. } else if (entry.value <= 0xffff) {
  164. writeByte(0x11);
  165. writeBytes(entry.value, 2);
  166. } else if (entry.value <= 0xffffffff) {
  167. writeByte(0x12);
  168. writeBytes(entry.value, 4);
  169. } else {
  170. writeByte(0x13);
  171. writeBytes(entry.value, 8);
  172. }
  173. } else {
  174. writeByte(0x23);
  175. writeDouble(entry.value);
  176. }
  177. }
  178. function writeUID(entry) {
  179. if (debug) {
  180. console.log('0x' + buffer.size().toString(16), 'writeUID', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')');
  181. }
  182. writeIntHeader(0x8, 0x0);
  183. writeID(entry.value);
  184. }
  185. function writeArray(entry) {
  186. if (debug) {
  187. console.log('0x' + buffer.size().toString(16), 'writeArray (length: ' + entry.entries.length + ')', '(id: ' + entry.id + ')');
  188. }
  189. writeIntHeader(0xA, entry.entries.length);
  190. entry.entries.forEach(function(e) {
  191. writeID(e.id);
  192. });
  193. }
  194. function writeBoolean(entry) {
  195. if (debug) {
  196. console.log('0x' + buffer.size().toString(16), 'writeBoolean', entry.value, '(id: ' + entry.id + ')');
  197. }
  198. writeByte(entry.value ? 0x09 : 0x08);
  199. }
  200. function writeString(entry) {
  201. if (debug) {
  202. console.log('0x' + buffer.size().toString(16), 'writeString', entry.value, '(id: ' + entry.id + ')');
  203. }
  204. if (entry.type === 'string-utf16' || mustBeUtf16(entry.value)) {
  205. var utf16 = new Buffer(entry.value, 'ucs2');
  206. writeIntHeader(0x6, utf16.length / 2);
  207. // needs to be big endian so swap the bytes
  208. for (var i = 0; i < utf16.length; i += 2) {
  209. var t = utf16[i + 0];
  210. utf16[i + 0] = utf16[i + 1];
  211. utf16[i + 1] = t;
  212. }
  213. buffer.write(utf16);
  214. } else {
  215. var utf8 = new Buffer(entry.value, 'ascii');
  216. writeIntHeader(0x5, utf8.length);
  217. buffer.write(utf8);
  218. }
  219. }
  220. function writeData(entry) {
  221. if (debug) {
  222. console.log('0x' + buffer.size().toString(16), 'writeData', entry.value, '(id: ' + entry.id + ')');
  223. }
  224. writeIntHeader(0x4, entry.value.length);
  225. buffer.write(entry.value);
  226. }
  227. function writeLong(l) {
  228. writeBytes(l, 8);
  229. }
  230. function writeByte(b) {
  231. buffer.write(new Buffer([b]));
  232. }
  233. function writeDouble(v) {
  234. var buf = new Buffer(8);
  235. buf.writeDoubleBE(v, 0);
  236. buffer.write(buf);
  237. }
  238. function writeIntHeader(kind, value) {
  239. if (value < 15) {
  240. writeByte((kind << 4) + value);
  241. } else if (value < 256) {
  242. writeByte((kind << 4) + 15);
  243. writeByte(0x10);
  244. writeBytes(value, 1);
  245. } else if (value < 65536) {
  246. writeByte((kind << 4) + 15);
  247. writeByte(0x11);
  248. writeBytes(value, 2);
  249. } else {
  250. writeByte((kind << 4) + 15);
  251. writeByte(0x12);
  252. writeBytes(value, 4);
  253. }
  254. }
  255. function writeID(id) {
  256. writeBytes(id, idSizeInBytes);
  257. }
  258. function writeBytes(value, bytes) {
  259. // write low-order bytes big-endian style
  260. var buf = new Buffer(bytes);
  261. var z = 0;
  262. // javascript doesn't handle large numbers
  263. while (bytes > 4) {
  264. buf[z++] = 0;
  265. bytes--;
  266. }
  267. for (var i = bytes - 1; i >= 0; i--) {
  268. buf[z++] = value >> (8 * i);
  269. }
  270. buffer.write(buf);
  271. }
  272. function mustBeUtf16(string) {
  273. return Buffer.byteLength(string, 'utf8') != string.length;
  274. }
  275. };
  276. function toEntries(dicts) {
  277. if (dicts.bplistOverride) {
  278. return [dicts];
  279. }
  280. if (dicts instanceof Array) {
  281. return toEntriesArray(dicts);
  282. } else if (dicts instanceof Buffer) {
  283. return [
  284. {
  285. type: 'data',
  286. value: dicts
  287. }
  288. ];
  289. } else if (dicts instanceof Real) {
  290. return [
  291. {
  292. type: 'double',
  293. value: dicts.value
  294. }
  295. ];
  296. } else if (typeof(dicts) === 'object') {
  297. if (dicts instanceof Date) {
  298. return [
  299. {
  300. type: 'date',
  301. value: dicts
  302. }
  303. ]
  304. } else if (Object.keys(dicts).length == 1 && typeof(dicts.UID) === 'number') {
  305. return [
  306. {
  307. type: 'UID',
  308. value: dicts.UID
  309. }
  310. ]
  311. } else {
  312. return toEntriesObject(dicts);
  313. }
  314. } else if (typeof(dicts) === 'string') {
  315. return [
  316. {
  317. type: 'string',
  318. value: dicts
  319. }
  320. ];
  321. } else if (typeof(dicts) === 'number') {
  322. return [
  323. {
  324. type: 'number',
  325. value: dicts
  326. }
  327. ];
  328. } else if (typeof(dicts) === 'boolean') {
  329. return [
  330. {
  331. type: 'boolean',
  332. value: dicts
  333. }
  334. ];
  335. } else {
  336. throw new Error('unhandled entry: ' + dicts);
  337. }
  338. }
  339. function toEntriesArray(arr) {
  340. if (debug) {
  341. console.log('toEntriesArray');
  342. }
  343. var results = [
  344. {
  345. type: 'array',
  346. entries: []
  347. }
  348. ];
  349. arr.forEach(function(v) {
  350. var entry = toEntries(v);
  351. results[0].entries.push(entry[0]);
  352. results = results.concat(entry);
  353. });
  354. return results;
  355. }
  356. function toEntriesObject(dict) {
  357. if (debug) {
  358. console.log('toEntriesObject');
  359. }
  360. var results = [
  361. {
  362. type: 'dict',
  363. entryKeys: [],
  364. entryValues: []
  365. }
  366. ];
  367. Object.keys(dict).forEach(function(key) {
  368. var entryKey = toEntries(key);
  369. results[0].entryKeys.push(entryKey[0]);
  370. results = results.concat(entryKey[0]);
  371. });
  372. Object.keys(dict).forEach(function(key) {
  373. var entryValue = toEntries(dict[key]);
  374. results[0].entryValues.push(entryValue[0]);
  375. results = results.concat(entryValue);
  376. });
  377. return results;
  378. }
  379. function computeOffsetSizeInBytes(maxOffset) {
  380. if (maxOffset < 256) {
  381. return 1;
  382. }
  383. if (maxOffset < 65536) {
  384. return 2;
  385. }
  386. if (maxOffset < 4294967296) {
  387. return 4;
  388. }
  389. return 8;
  390. }
  391. function computeIdSizeInBytes(numberOfIds) {
  392. if (numberOfIds < 256) {
  393. return 1;
  394. }
  395. if (numberOfIds < 65536) {
  396. return 2;
  397. }
  398. return 4;
  399. }
  400. module.exports.Real = Real;