Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

bplistCreator.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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, true);
  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(0x14);
  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, is_signedint) {
  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. if(!is_signedint) {
  264. while (bytes > 4) {
  265. buf[z++] = 0;
  266. bytes--;
  267. }
  268. }
  269. for (var i = bytes - 1; i >= 0; i--) {
  270. buf[z++] = value >> (8 * i);
  271. }
  272. buffer.write(buf);
  273. }
  274. function mustBeUtf16(string) {
  275. return Buffer.byteLength(string, 'utf8') != string.length;
  276. }
  277. };
  278. function toEntries(dicts) {
  279. if (dicts.bplistOverride) {
  280. return [dicts];
  281. }
  282. if (dicts instanceof Array) {
  283. return toEntriesArray(dicts);
  284. } else if (dicts instanceof Buffer) {
  285. return [
  286. {
  287. type: 'data',
  288. value: dicts
  289. }
  290. ];
  291. } else if (dicts instanceof Real) {
  292. return [
  293. {
  294. type: 'double',
  295. value: dicts.value
  296. }
  297. ];
  298. } else if (typeof(dicts) === 'object') {
  299. if (dicts instanceof Date) {
  300. return [
  301. {
  302. type: 'date',
  303. value: dicts
  304. }
  305. ]
  306. } else if (Object.keys(dicts).length == 1 && typeof(dicts.UID) === 'number') {
  307. return [
  308. {
  309. type: 'UID',
  310. value: dicts.UID
  311. }
  312. ]
  313. } else {
  314. return toEntriesObject(dicts);
  315. }
  316. } else if (typeof(dicts) === 'string') {
  317. return [
  318. {
  319. type: 'string',
  320. value: dicts
  321. }
  322. ];
  323. } else if (typeof(dicts) === 'number') {
  324. return [
  325. {
  326. type: 'number',
  327. value: dicts
  328. }
  329. ];
  330. } else if (typeof(dicts) === 'boolean') {
  331. return [
  332. {
  333. type: 'boolean',
  334. value: dicts
  335. }
  336. ];
  337. } else if (typeof(dicts) === 'bigint') {
  338. return [
  339. {
  340. type: 'number',
  341. value: Number(BigInt.asIntN(32, dicts))
  342. }
  343. ];
  344. } else {
  345. throw new Error('unhandled entry: ' + dicts);
  346. }
  347. }
  348. function toEntriesArray(arr) {
  349. if (debug) {
  350. console.log('toEntriesArray');
  351. }
  352. var results = [
  353. {
  354. type: 'array',
  355. entries: []
  356. }
  357. ];
  358. arr.forEach(function(v) {
  359. var entry = toEntries(v);
  360. results[0].entries.push(entry[0]);
  361. results = results.concat(entry);
  362. });
  363. return results;
  364. }
  365. function toEntriesObject(dict) {
  366. if (debug) {
  367. console.log('toEntriesObject');
  368. }
  369. var results = [
  370. {
  371. type: 'dict',
  372. entryKeys: [],
  373. entryValues: []
  374. }
  375. ];
  376. Object.keys(dict).forEach(function(key) {
  377. var entryKey = toEntries(key);
  378. results[0].entryKeys.push(entryKey[0]);
  379. results = results.concat(entryKey[0]);
  380. });
  381. Object.keys(dict).forEach(function(key) {
  382. var entryValue = toEntries(dict[key]);
  383. results[0].entryValues.push(entryValue[0]);
  384. results = results.concat(entryValue);
  385. });
  386. return results;
  387. }
  388. function computeOffsetSizeInBytes(maxOffset) {
  389. if (maxOffset < 256) {
  390. return 1;
  391. }
  392. if (maxOffset < 65536) {
  393. return 2;
  394. }
  395. if (maxOffset < 4294967296) {
  396. return 4;
  397. }
  398. return 8;
  399. }
  400. function computeIdSizeInBytes(numberOfIds) {
  401. if (numberOfIds < 256) {
  402. return 1;
  403. }
  404. if (numberOfIds < 65536) {
  405. return 2;
  406. }
  407. return 4;
  408. }
  409. module.exports.Real = Real;