Class: PropertyList::BinaryGenerator
- Inherits:
-
Object
- Object
- PropertyList::BinaryGenerator
- Includes:
- BinaryMarkers
- Defined in:
- lib/property-list/binary_generator.rb
Overview
Modified from:
https://github.com/jarib/plist/blob/master/lib/plist/binary.rb
With improved performance
Defined Under Namespace
Classes: Collection
Constant Summary collapse
- V0_MAGIC =
"bplist00".force_encoding('binary').freeze
- V1_MAGIC =
"bplist10".force_encoding('binary').freeze
- TAIL_PADDING =
"\0\0\0\0\0\0".force_encoding('binary').freeze
Constants included from BinaryMarkers
PropertyList::BinaryMarkers::MARKER_ARRAY, PropertyList::BinaryMarkers::MARKER_ASCII_STRING, PropertyList::BinaryMarkers::MARKER_DATA, PropertyList::BinaryMarkers::MARKER_DATE, PropertyList::BinaryMarkers::MARKER_DICT, PropertyList::BinaryMarkers::MARKER_FALSE, PropertyList::BinaryMarkers::MARKER_FILL, PropertyList::BinaryMarkers::MARKER_INT, PropertyList::BinaryMarkers::MARKER_NO_BASE_URL, PropertyList::BinaryMarkers::MARKER_NULL, PropertyList::BinaryMarkers::MARKER_ORD_SET, PropertyList::BinaryMarkers::MARKER_REAL, PropertyList::BinaryMarkers::MARKER_SET, PropertyList::BinaryMarkers::MARKER_TRUE, PropertyList::BinaryMarkers::MARKER_UID, PropertyList::BinaryMarkers::MARKER_UTF16BE_STRING, PropertyList::BinaryMarkers::MARKER_UTF8_STRING, PropertyList::BinaryMarkers::MARKER_UUID, PropertyList::BinaryMarkers::MARKER_WITH_BASE_URL, PropertyList::BinaryMarkers::TIME_INTERVAL_SINCE_1970
Instance Attribute Summary collapse
-
#output ⇒ Object
readonly
Returns the value of attribute output.
Instance Method Summary collapse
- #add_output(data) ⇒ Object
-
#binary_integer(i, num_bytes) ⇒ Object
Packs an integer
i
into its binary representation in the specified number of bytes. - #binary_marker(marker, size) ⇒ Object
- #binary_object(obj, ref_byte_size = 4) ⇒ Object
- #binary_string(obj) ⇒ Object
- #binary_url(obj) ⇒ Object
- #chr(c) ⇒ Object
- #flatten(obj) ⇒ Object
- #generate(obj) ⇒ Object
-
#initialize(opts) ⇒ BinaryGenerator
constructor
A new instance of BinaryGenerator.
-
#min_byte_size(i) ⇒ Object
Determines the minimum number of bytes that is a power of two and can represent the integer
i
. - #pack(objs, format) ⇒ Object
Constructor Details
#initialize(opts) ⇒ BinaryGenerator
Returns a new instance of BinaryGenerator.
25 26 27 28 29 30 |
# File 'lib/property-list/binary_generator.rb', line 25 def initialize opts @output = [] @offset = 0 @objs = [] @ref_size = 0 end |
Instance Attribute Details
#output ⇒ Object (readonly)
Returns the value of attribute output.
31 32 33 |
# File 'lib/property-list/binary_generator.rb', line 31 def output @output end |
Instance Method Details
#add_output(data) ⇒ Object
111 112 113 114 |
# File 'lib/property-list/binary_generator.rb', line 111 def add_output data @output << data @offset += data.bytesize end |
#binary_integer(i, num_bytes) ⇒ Object
Packs an integer i
into its binary representation in the specified number of bytes. Byte order is big-endian. Negative integers cannot be stored in 1, 2, or 4 bytes.
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/property-list/binary_generator.rb', line 223 def binary_integer i, num_bytes if i < 0 && num_bytes < 8 raise ArgumentError, "negative integers require 8 or 16 bytes of storage" end case num_bytes when 1 add_output pack([i], "C") when 2 add_output pack([i], "n") when 4 add_output pack([i], "N") when 8 add_output pack([i], "q>") when 16 # TODO verify 128 bit integer encoding if i < 0 i = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff ^ i.abs + 1 end add_output pack([i >> 64, i & 0xffff_ffff_ffff_ffff], "q>2") else raise ArgumentError, "num_bytes must be 1, 2, 4, 8, or 16" end end |
#binary_marker(marker, size) ⇒ Object
177 178 179 180 181 182 183 184 |
# File 'lib/property-list/binary_generator.rb', line 177 def binary_marker marker, size if size < 15 add_output chr(marker | size) else add_output chr(marker | 0xf) binary_object size end end |
#binary_object(obj, ref_byte_size = 4) ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/property-list/binary_generator.rb', line 116 def binary_object obj, ref_byte_size = 4 case obj when Symbol binary_string obj.to_s when String binary_string obj when Float add_output pack([(MARKER_REAL | 3), obj], "CG") when Integer nbytes = min_byte_size obj size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes] add_output chr(MARKER_INT | size_bits) binary_integer obj, nbytes when TrueClass add_output chr(MARKER_TRUE) when FalseClass add_output chr(MARKER_FALSE) when Time add_output pack([MARKER_DATE, obj.to_f - TIME_INTERVAL_SINCE_1970], "CG") when Date # also covers DateTime add_output pack([MARKER_DATE, obj.to_time.to_f - TIME_INTERVAL_SINCE_1970], "CG") when StringIO data = obj.string.force_encoding('binary') # NOTE StringIO.binmode doesn't work in <2.0.0 & rbx binary_marker MARKER_DATA, data.bytesize add_output data when IO obj.rewind obj.binmode data = obj.read binary_marker MARKER_DATA, data.bytesize add_output data when Collection binary_marker obj.marker, obj.size obj.refs.each do |i| binary_integer i, ref_byte_size end when Uid # Encoding is as integers, except values are unsigned. nbytes = min_byte_size obj.uid size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes] add_output chr(MARKER_UID | size_bits) binary_integer obj.uid, nbytes ## the following are v1x data types ## when Uuid @v1 = true data = pack([obj.uuid], 'H*') add_output chr(MARKER_UUID) add_output data when Url @v1 = true binary_url obj when NilClass @v1 = true add_output chr(MARKER_NULL) else raise UnsupportedTypeError, obj.class.to_s end end |
#binary_string(obj) ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/property-list/binary_generator.rb', line 186 def binary_string obj if obj.encoding == Encoding.find('binary') binary_marker MARKER_ASCII_STRING, obj.bytesize add_output obj elsif obj.ascii_only? obj = obj.dup.force_encoding 'binary' binary_marker MARKER_ASCII_STRING, obj.bytesize add_output obj else data = obj.encode('utf-16be').force_encoding 'binary' cp_size = data.bytesize / 2 binary_marker MARKER_UTF16BE_STRING, cp_size # TODO check if it works for 4 bytes add_output data end end |
#binary_url(obj) ⇒ Object
202 203 204 205 206 207 208 209 210 |
# File 'lib/property-list/binary_generator.rb', line 202 def binary_url obj if obj.url =~ /\A\w+:\/\// add_output chr(MARKER_WITH_BASE_URL) else add_output chr(MARKER_NO_BASE_URL) end binary_marker MARKER_ASCII_STRING, obj.url.bytesize add_output obj.url end |
#chr(c) ⇒ Object
212 213 214 |
# File 'lib/property-list/binary_generator.rb', line 212 def chr c c.chr.force_encoding 'binary' end |
#flatten(obj) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/property-list/binary_generator.rb', line 63 def flatten obj @ref_size += 1 case obj when Array refs = [] @objs << Collection[MARKER_ARRAY, obj.size, refs] obj.each do |e| refs << @ref_size flatten e end when Set @v1 = true refs = [] @objs << Collection[MARKER_SET, obj.size, refs] obj.each do |e| refs << @ref_size flatten e end when Hash refs = [] @objs << Collection[MARKER_DICT, obj.size, refs] obj.each do |e, _| refs << @ref_size flatten e end obj.each do |_, e| refs << @ref_size flatten e end when OrdSet @v1 = true refs = [] elements = obj.elements @objs << Collection[MARKER_ORD_SET, elements.size, refs] elements.each do |e| refs << @ref_size flatten e end else @objs << obj end end |
#generate(obj) ⇒ Object
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 59 60 61 |
# File 'lib/property-list/binary_generator.rb', line 33 def generate obj flatten obj ref_byte_size = min_byte_size @ref_size - 1 add_output V0_MAGIC offset_table = [] @objs.each do |o| offset_table << @offset binary_object o, ref_byte_size end if @v1 @output[0] = V1_MAGIC end offset_table_addr = @offset offset_byte_size = min_byte_size @offset offset_table.each do |offset| binary_integer offset, offset_byte_size end add_output pack([ TAIL_PADDING, offset_byte_size, ref_byte_size, @ref_size, 0, # index of root object offset_table_addr ], "a*C2Q>3") end |
#min_byte_size(i) ⇒ Object
Determines the minimum number of bytes that is a power of two and can represent the integer i
. Raises a RangeError if the number of bytes exceeds 16. Note that the property list format considers integers of 1, 2, and 4 bytes to be unsigned, while 8- and 16-byte integers are signed; thus negative integers will always require at least 8 bytes of storage.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/property-list/binary_generator.rb', line 252 def min_byte_size i if i < 0 i = i.abs - 1 else if i <= 0xff return 1 elsif i <= 0xffff return 2 elsif i <= 0xffffffff return 4 end end if i <= 0x7fffffffffffffff 8 elsif i <= 0x7fffffffffffffffffffffffffffffff 16 else raise RangeError, "integer too big - exceeds 128 bits" end end |
#pack(objs, format) ⇒ Object
216 217 218 |
# File 'lib/property-list/binary_generator.rb', line 216 def pack objs, format objs.pack(format).force_encoding 'binary' end |