123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- 'use strict';
-
- // adapted from http://code.google.com/p/plist/source/browse/trunk/src/main/java/com/dd/plist/BinaryPropertyListWriter.java
-
- var streamBuffers = require("stream-buffers");
-
- var debug = false;
-
- function Real(value) {
- this.value = value;
- }
-
- module.exports = function(dicts) {
- var buffer = new streamBuffers.WritableStreamBuffer();
- buffer.write(new Buffer("bplist00"));
-
- if (debug) {
- console.log('create', require('util').inspect(dicts, false, 10));
- }
-
- if (dicts instanceof Array && dicts.length === 1) {
- dicts = dicts[0];
- }
-
- var entries = toEntries(dicts);
- if (debug) {
- console.log('entries', entries);
- }
- var idSizeInBytes = computeIdSizeInBytes(entries.length);
- var offsets = [];
- var offsetSizeInBytes;
- var offsetTableOffset;
-
- updateEntryIds();
-
- entries.forEach(function(entry, entryIdx) {
- offsets[entryIdx] = buffer.size();
- if (!entry) {
- buffer.write(0x00);
- } else {
- write(entry);
- }
- });
-
- writeOffsetTable();
- writeTrailer();
- return buffer.getContents();
-
- function updateEntryIds() {
- var strings = {};
- var entryId = 0;
- entries.forEach(function(entry) {
- if (entry.id) {
- return;
- }
- if (entry.type === 'string') {
- if (!entry.bplistOverride && strings.hasOwnProperty(entry.value)) {
- entry.type = 'stringref';
- entry.id = strings[entry.value];
- } else {
- strings[entry.value] = entry.id = entryId++;
- }
- } else {
- entry.id = entryId++;
- }
- });
-
- entries = entries.filter(function(entry) {
- return (entry.type !== 'stringref');
- });
- }
-
- function writeTrailer() {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer');
- }
- // 6 null bytes
- buffer.write(new Buffer([0, 0, 0, 0, 0, 0]));
-
- // size of an offset
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', offsetSizeInBytes);
- }
- writeByte(offsetSizeInBytes);
-
- // size of a ref
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', idSizeInBytes);
- }
- writeByte(idSizeInBytes);
-
- // number of objects
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer(number of objects):', entries.length);
- }
- writeLong(entries.length);
-
- // top object
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer(top object)');
- }
- writeLong(0);
-
- // offset table offset
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeTrailer(offset table offset):', offsetTableOffset);
- }
- writeLong(offsetTableOffset);
- }
-
- function writeOffsetTable() {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeOffsetTable');
- }
- offsetTableOffset = buffer.size();
- offsetSizeInBytes = computeOffsetSizeInBytes(offsetTableOffset);
- offsets.forEach(function(offset) {
- writeBytes(offset, offsetSizeInBytes);
- });
- }
-
- function write(entry) {
- switch (entry.type) {
- case 'dict':
- writeDict(entry);
- break;
- case 'number':
- case 'double':
- writeNumber(entry);
- break;
- case 'UID':
- writeUID(entry);
- break;
- case 'array':
- writeArray(entry);
- break;
- case 'boolean':
- writeBoolean(entry);
- break;
- case 'string':
- case 'string-utf16':
- writeString(entry);
- break;
- case 'date':
- writeDate(entry);
- break;
- case 'data':
- writeData(entry);
- break;
- default:
- throw new Error("unhandled entry type: " + entry.type);
- }
- }
-
- function writeDate(entry) {
- writeByte(0x33);
- var date = (Date.parse(entry.value)/1000) - 978307200
- writeDouble(date)
- }
-
- function writeDict(entry) {
- if (debug) {
- var keysStr = entry.entryKeys.map(function(k) {return k.id;});
- var valsStr = entry.entryValues.map(function(k) {return k.id;});
- console.log('0x' + buffer.size().toString(16), 'writeDict', '(id: ' + entry.id + ')', '(keys: ' + keysStr + ')', '(values: ' + valsStr + ')');
- }
- writeIntHeader(0xD, entry.entryKeys.length);
- entry.entryKeys.forEach(function(entry) {
- writeID(entry.id);
- });
- entry.entryValues.forEach(function(entry) {
- writeID(entry.id);
- });
- }
-
- function writeNumber(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeNumber', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')');
- }
-
- if (entry.type !== 'double' && parseFloat(entry.value.toFixed()) == entry.value) {
- if (entry.value < 0) {
- writeByte(0x13);
- writeBytes(entry.value, 8);
- } else if (entry.value <= 0xff) {
- writeByte(0x10);
- writeBytes(entry.value, 1);
- } else if (entry.value <= 0xffff) {
- writeByte(0x11);
- writeBytes(entry.value, 2);
- } else if (entry.value <= 0xffffffff) {
- writeByte(0x12);
- writeBytes(entry.value, 4);
- } else {
- writeByte(0x13);
- writeBytes(entry.value, 8);
- }
- } else {
- writeByte(0x23);
- writeDouble(entry.value);
- }
- }
-
- function writeUID(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeUID', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')');
- }
-
- writeIntHeader(0x8, 0x0);
- writeID(entry.value);
- }
-
- function writeArray(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeArray (length: ' + entry.entries.length + ')', '(id: ' + entry.id + ')');
- }
- writeIntHeader(0xA, entry.entries.length);
- entry.entries.forEach(function(e) {
- writeID(e.id);
- });
- }
-
- function writeBoolean(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeBoolean', entry.value, '(id: ' + entry.id + ')');
- }
- writeByte(entry.value ? 0x09 : 0x08);
- }
-
- function writeString(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeString', entry.value, '(id: ' + entry.id + ')');
- }
- if (entry.type === 'string-utf16' || mustBeUtf16(entry.value)) {
- var utf16 = new Buffer(entry.value, 'ucs2');
- writeIntHeader(0x6, utf16.length / 2);
- // needs to be big endian so swap the bytes
- for (var i = 0; i < utf16.length; i += 2) {
- var t = utf16[i + 0];
- utf16[i + 0] = utf16[i + 1];
- utf16[i + 1] = t;
- }
- buffer.write(utf16);
- } else {
- var utf8 = new Buffer(entry.value, 'ascii');
- writeIntHeader(0x5, utf8.length);
- buffer.write(utf8);
- }
- }
-
- function writeData(entry) {
- if (debug) {
- console.log('0x' + buffer.size().toString(16), 'writeData', entry.value, '(id: ' + entry.id + ')');
- }
- writeIntHeader(0x4, entry.value.length);
- buffer.write(entry.value);
- }
-
- function writeLong(l) {
- writeBytes(l, 8);
- }
-
- function writeByte(b) {
- buffer.write(new Buffer([b]));
- }
-
- function writeDouble(v) {
- var buf = new Buffer(8);
- buf.writeDoubleBE(v, 0);
- buffer.write(buf);
- }
-
- function writeIntHeader(kind, value) {
- if (value < 15) {
- writeByte((kind << 4) + value);
- } else if (value < 256) {
- writeByte((kind << 4) + 15);
- writeByte(0x10);
- writeBytes(value, 1);
- } else if (value < 65536) {
- writeByte((kind << 4) + 15);
- writeByte(0x11);
- writeBytes(value, 2);
- } else {
- writeByte((kind << 4) + 15);
- writeByte(0x12);
- writeBytes(value, 4);
- }
- }
-
- function writeID(id) {
- writeBytes(id, idSizeInBytes);
- }
-
- function writeBytes(value, bytes) {
- // write low-order bytes big-endian style
- var buf = new Buffer(bytes);
- var z = 0;
- // javascript doesn't handle large numbers
- while (bytes > 4) {
- buf[z++] = 0;
- bytes--;
- }
- for (var i = bytes - 1; i >= 0; i--) {
- buf[z++] = value >> (8 * i);
- }
- buffer.write(buf);
- }
-
- function mustBeUtf16(string) {
- return Buffer.byteLength(string, 'utf8') != string.length;
- }
- };
-
- function toEntries(dicts) {
- if (dicts.bplistOverride) {
- return [dicts];
- }
-
- if (dicts instanceof Array) {
- return toEntriesArray(dicts);
- } else if (dicts instanceof Buffer) {
- return [
- {
- type: 'data',
- value: dicts
- }
- ];
- } else if (dicts instanceof Real) {
- return [
- {
- type: 'double',
- value: dicts.value
- }
- ];
- } else if (typeof(dicts) === 'object') {
- if (dicts instanceof Date) {
- return [
- {
- type: 'date',
- value: dicts
- }
- ]
- } else if (Object.keys(dicts).length == 1 && typeof(dicts.UID) === 'number') {
- return [
- {
- type: 'UID',
- value: dicts.UID
- }
- ]
- } else {
- return toEntriesObject(dicts);
- }
- } else if (typeof(dicts) === 'string') {
- return [
- {
- type: 'string',
- value: dicts
- }
- ];
- } else if (typeof(dicts) === 'number') {
- return [
- {
- type: 'number',
- value: dicts
- }
- ];
- } else if (typeof(dicts) === 'boolean') {
- return [
- {
- type: 'boolean',
- value: dicts
- }
- ];
- } else {
- throw new Error('unhandled entry: ' + dicts);
- }
- }
-
- function toEntriesArray(arr) {
- if (debug) {
- console.log('toEntriesArray');
- }
- var results = [
- {
- type: 'array',
- entries: []
- }
- ];
- arr.forEach(function(v) {
- var entry = toEntries(v);
- results[0].entries.push(entry[0]);
- results = results.concat(entry);
- });
- return results;
- }
-
- function toEntriesObject(dict) {
- if (debug) {
- console.log('toEntriesObject');
- }
- var results = [
- {
- type: 'dict',
- entryKeys: [],
- entryValues: []
- }
- ];
- Object.keys(dict).forEach(function(key) {
- var entryKey = toEntries(key);
- results[0].entryKeys.push(entryKey[0]);
- results = results.concat(entryKey[0]);
- });
- Object.keys(dict).forEach(function(key) {
- var entryValue = toEntries(dict[key]);
- results[0].entryValues.push(entryValue[0]);
- results = results.concat(entryValue);
- });
- return results;
- }
-
- function computeOffsetSizeInBytes(maxOffset) {
- if (maxOffset < 256) {
- return 1;
- }
- if (maxOffset < 65536) {
- return 2;
- }
- if (maxOffset < 4294967296) {
- return 4;
- }
- return 8;
- }
-
- function computeIdSizeInBytes(numberOfIds) {
- if (numberOfIds < 256) {
- return 1;
- }
- if (numberOfIds < 65536) {
- return 2;
- }
- return 4;
- }
-
- module.exports.Real = Real;
|