rb-scpt 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rb-scpt-1.0.1.gem +0 -0
  3. data/extconf.rb +12 -12
  4. data/rb-scpt.gemspec +10 -10
  5. data/sample/AB_export_vcard.rb +16 -16
  6. data/sample/AB_list_people_with_emails.rb +4 -4
  7. data/sample/Add_iCal_event.rb +12 -12
  8. data/sample/Create_daily_iCal_todos.rb +28 -28
  9. data/sample/Export_Address_Book_phone_numbers.rb +52 -52
  10. data/sample/Hello_world.rb +10 -10
  11. data/sample/List_iTunes_playlist_names.rb +3 -3
  12. data/sample/Make_Mail_message.rb +24 -24
  13. data/sample/Open_file_in_TextEdit.rb +5 -5
  14. data/sample/Organize_Mail_messages.rb +46 -46
  15. data/sample/Print_folder_tree.rb +5 -5
  16. data/sample/Select_all_HTML_files.rb +6 -6
  17. data/sample/Set_iChat_status.rb +12 -12
  18. data/sample/Simple_Finder_GUI_Scripting.rb +6 -6
  19. data/sample/Stagger_Finder_windows.rb +9 -9
  20. data/sample/TextEdit_demo.rb +71 -71
  21. data/sample/iTunes_top40_to_html.rb +28 -30
  22. data/src/SendThreadSafe.c +293 -293
  23. data/src/SendThreadSafe.h +108 -108
  24. data/src/lib/_aem/aemreference.rb +997 -998
  25. data/src/lib/_aem/codecs.rb +609 -610
  26. data/src/lib/_aem/connect.rb +197 -197
  27. data/src/lib/_aem/encodingsupport.rb +67 -67
  28. data/src/lib/_aem/findapp.rb +75 -75
  29. data/src/lib/_aem/mactypes.rb +241 -242
  30. data/src/lib/_aem/send.rb +268 -268
  31. data/src/lib/_aem/typewrappers.rb +52 -52
  32. data/src/lib/_appscript/defaultterminology.rb +266 -266
  33. data/src/lib/_appscript/referencerenderer.rb +230 -233
  34. data/src/lib/_appscript/reservedkeywords.rb +106 -106
  35. data/src/lib/_appscript/safeobject.rb +125 -125
  36. data/src/lib/_appscript/terminology.rb +448 -449
  37. data/src/lib/aem.rb +238 -238
  38. data/src/lib/kae.rb +1487 -1487
  39. data/src/lib/osax.rb +647 -647
  40. data/src/lib/rb-scpt.rb +1065 -1065
  41. data/src/rbae.c +595 -595
  42. data/test/test_aemreference.rb +104 -107
  43. data/test/test_appscriptcommands.rb +131 -134
  44. data/test/test_appscriptreference.rb +96 -99
  45. data/test/test_codecs.rb +166 -168
  46. data/test/test_findapp.rb +13 -16
  47. data/test/test_mactypes.rb +70 -72
  48. data/test/test_osax.rb +46 -48
  49. data/test/testall.sh +4 -4
  50. metadata +8 -7
@@ -22,80 +22,80 @@ require "_aem/encodingsupport"
22
22
 
23
23
 
24
24
  class UnitTypeCodecs
25
- # Provides pack and unpack methods for converting between MacTypes::Units instances
26
- # and AE unit types. Each Codecs instance is allocated its own UnitTypeCodecs instance,
27
- #
28
-
29
- DefaultUnitTypes = [
30
- [:centimeters, KAE::TypeCentimeters],
31
- [:meters, KAE::TypeMeters],
32
- [:kilometers, KAE::TypeKilometers],
33
- [:inches, KAE::TypeInches],
34
- [:feet, KAE::TypeFeet],
35
- [:yards, KAE::TypeYards],
36
- [:miles, KAE::TypeMiles],
37
-
38
- [:square_meters, KAE::TypeSquareMeters],
39
- [:square_kilometers, KAE::TypeSquareKilometers],
40
- [:square_feet, KAE::TypeSquareFeet],
41
- [:square_yards, KAE::TypeSquareYards],
42
- [:square_miles, KAE::TypeSquareMiles],
43
-
44
- [:cubic_centimeters, KAE::TypeCubicCentimeter],
45
- [:cubic_meters, KAE::TypeCubicMeters],
46
- [:cubic_inches, KAE::TypeCubicInches],
47
- [:cubic_feet, KAE::TypeCubicFeet],
48
- [:cubic_yards, KAE::TypeCubicYards],
49
-
50
- [:liters, KAE::TypeLiters],
51
- [:quarts, KAE::TypeQuarts],
52
- [:gallons, KAE::TypeGallons],
53
-
54
- [:grams, KAE::TypeGrams],
55
- [:kilograms, KAE::TypeKilograms],
56
- [:ounces, KAE::TypeOunces],
57
- [:pounds, KAE::TypePounds],
58
-
59
- [:degrees_Celsius, KAE::TypeDegreesC],
60
- [:degrees_Fahrenheit, KAE::TypeDegreesF],
61
- [:degrees_Kelvin, KAE::TypeDegreesK],
62
- ]
63
-
64
- DefaultPacker = proc { |units, code| AE::AEDesc.new(code, [units.value].pack('d')) }
65
- DefaultUnpacker = proc { |desc, name| MacTypes::Units.new(desc.data.unpack('d')[0], name) }
66
-
67
- def initialize
68
- @type_by_name = {}
69
- @type_by_code = {}
70
- add_types(DefaultUnitTypes)
71
- end
72
-
73
- def add_types(type_defs)
74
- # type_defs is a list of lists, where each sublist is of form:
75
- # [typename, typecode, packproc, unpackproc]
76
- # or:
77
- # [typename, typecode]
78
- # If optional packproc and unpackproc are omitted, default pack/unpack procs
79
- # are used instead; these pack/unpack AEDesc data as a double precision float.
80
- type_defs.each do |name, code, packer, unpacker|
81
- @type_by_name[name] = [code, (packer or DefaultPacker)]
82
- @type_by_code[code] = [name, (unpacker or DefaultUnpacker)]
83
- end
84
- end
85
-
86
- def pack(val)
87
- if val.is_a?(MacTypes::Units)
88
- code, packer = @type_by_name.fetch(val.type) { |v| raise IndexError, "Unknown unit type: #{v.inspect}" }
89
- return [true, packer.call(val, code)]
90
- else
91
- return [false, val]
92
- end
93
- end
94
-
95
- def unpack(desc)
96
- name, unpacker = @type_by_code.fetch(desc.type) { |d| return [false, d] }
97
- return [true, unpacker.call(desc, name)]
98
- end
25
+ # Provides pack and unpack methods for converting between MacTypes::Units instances
26
+ # and AE unit types. Each Codecs instance is allocated its own UnitTypeCodecs instance,
27
+ #
28
+
29
+ DefaultUnitTypes = [
30
+ [:centimeters, KAE::TypeCentimeters],
31
+ [:meters, KAE::TypeMeters],
32
+ [:kilometers, KAE::TypeKilometers],
33
+ [:inches, KAE::TypeInches],
34
+ [:feet, KAE::TypeFeet],
35
+ [:yards, KAE::TypeYards],
36
+ [:miles, KAE::TypeMiles],
37
+
38
+ [:square_meters, KAE::TypeSquareMeters],
39
+ [:square_kilometers, KAE::TypeSquareKilometers],
40
+ [:square_feet, KAE::TypeSquareFeet],
41
+ [:square_yards, KAE::TypeSquareYards],
42
+ [:square_miles, KAE::TypeSquareMiles],
43
+
44
+ [:cubic_centimeters, KAE::TypeCubicCentimeter],
45
+ [:cubic_meters, KAE::TypeCubicMeters],
46
+ [:cubic_inches, KAE::TypeCubicInches],
47
+ [:cubic_feet, KAE::TypeCubicFeet],
48
+ [:cubic_yards, KAE::TypeCubicYards],
49
+
50
+ [:liters, KAE::TypeLiters],
51
+ [:quarts, KAE::TypeQuarts],
52
+ [:gallons, KAE::TypeGallons],
53
+
54
+ [:grams, KAE::TypeGrams],
55
+ [:kilograms, KAE::TypeKilograms],
56
+ [:ounces, KAE::TypeOunces],
57
+ [:pounds, KAE::TypePounds],
58
+
59
+ [:degrees_Celsius, KAE::TypeDegreesC],
60
+ [:degrees_Fahrenheit, KAE::TypeDegreesF],
61
+ [:degrees_Kelvin, KAE::TypeDegreesK],
62
+ ]
63
+
64
+ DefaultPacker = proc { |units, code| AE::AEDesc.new(code, [units.value].pack('d')) }
65
+ DefaultUnpacker = proc { |desc, name| MacTypes::Units.new(desc.data.unpack('d')[0], name) }
66
+
67
+ def initialize
68
+ @type_by_name = {}
69
+ @type_by_code = {}
70
+ add_types(DefaultUnitTypes)
71
+ end
72
+
73
+ def add_types(type_defs)
74
+ # type_defs is a list of lists, where each sublist is of form:
75
+ # [typename, typecode, packproc, unpackproc]
76
+ # or:
77
+ # [typename, typecode]
78
+ # If optional packproc and unpackproc are omitted, default pack/unpack procs
79
+ # are used instead; these pack/unpack AEDesc data as a double precision float.
80
+ type_defs.each do |name, code, packer, unpacker|
81
+ @type_by_name[name] = [code, (packer or DefaultPacker)]
82
+ @type_by_code[code] = [name, (unpacker or DefaultUnpacker)]
83
+ end
84
+ end
85
+
86
+ def pack(val)
87
+ if val.is_a?(MacTypes::Units)
88
+ code, packer = @type_by_name.fetch(val.type) { |v| raise IndexError, "Unknown unit type: #{v.inspect}" }
89
+ return [true, packer.call(val, code)]
90
+ else
91
+ return [false, val]
92
+ end
93
+ end
94
+
95
+ def unpack(desc)
96
+ name, unpacker = @type_by_code.fetch(desc.type) { |d| return [false, d] }
97
+ return [true, unpacker.call(desc, name)]
98
+ end
99
99
  end
100
100
 
101
101
 
@@ -105,558 +105,557 @@ end
105
105
  # Endianness support
106
106
 
107
107
  module BigEndianConverters
108
-
109
- def four_char_code(code)
110
- return code
111
- end
108
+
109
+ def four_char_code(code)
110
+ return code
111
+ end
112
112
 
113
113
  end
114
114
 
115
115
 
116
116
  module SmallEndianConverters
117
-
118
- def four_char_code(code)
119
- return code.reverse
120
- end
121
-
117
+
118
+ def four_char_code(code)
119
+ return code.reverse
120
+ end
121
+
122
122
  end
123
123
 
124
124
 
125
125
  ######################################################################
126
126
 
127
127
  module DisableObjectSpecifierCaching
128
- def unpack_object_specifier(desc)
129
- return fully_unpack_object_specifier(desc)
130
- end
128
+ def unpack_object_specifier(desc)
129
+ return fully_unpack_object_specifier(desc)
130
+ end
131
131
  end
132
132
 
133
133
  #######
134
134
 
135
135
  class Codecs
136
- # Provides pack and unpack methods for converting data between Ruby and AE types.
137
- #
138
- # May be subclassed to extend/alter its behaviour (e.g. the appscript layer does this).
139
- # Conversions that are most likely to be modified (e.g. for packing and and unpacking
140
- # references, records, types and enums) are exposed as overrideable hook methods.
141
-
142
- extend([1].pack('s') == "\001\000" ? SmallEndianConverters : BigEndianConverters)
143
-
144
- def initialize
145
- @unit_type_codecs = UnitTypeCodecs.new
146
- # Note: while typeUnicodeText is deprecated (see AEDataModel.h), it's still the
147
- # most commonly used Unicode type so is used here for compatibility's sake.
148
- # typeUTF8Text was initially tried, but existing applications had problems with it; i.e.
149
- # some apps make unsafe assumptions on what to expect based on AS's behaviour.
150
- # Once AppleScript is using typeUTF8Text/typeUTF16ExternalRepresentation
151
- # and existing applications don't choke, this code can be similarly upgraded.
152
- @pack_text_as_type = KAE::TypeUnicodeText
153
- # on Ruby 1.9+, set String encoding to UTF-8
154
- @encoding_support = AEMEncodingSupport.encoding_support
155
- @unpack_dates_as_datetime = false
156
- end
157
-
158
- ######################################################################
159
- # Compatibility options
160
-
161
- def add_unit_types(type_defs)
162
- # register custom unit type definitions with this Codecs instance
163
- # e.g. Adobe apps define additional unit types (ciceros, pixels, etc.)
164
- @unit_type_codecs.add_types(type_defs)
165
- end
166
-
167
- def dont_cache_unpacked_specifiers
168
- # When unpacking object specifiers, unlike AppleScript, appscript caches
169
- # the original AEDesc for efficiency, allowing the resulting reference to
170
- # be re-packed much more quickly. Occasionally this causes compatibility
171
- # problems with applications that returned subtly malformed specifiers.
172
- # To force a Codecs object to fully unpack and repack object specifiers,
173
- # call its dont_cache_unpacked_specifiers method.
174
- extend(DisableObjectSpecifierCaching)
175
- end
176
-
177
- def pack_strings_as_type(code)
178
- # Some older (pre-OS X) applications may require text to be passed as
179
- # typeChar or typeIntlText rather than the usual typeUnicodeText. To force
180
- # an AEM::Codecs object to pack strings as one of these older types, call
181
- # its pack_strings_as_type method, specifying the type you want used instead.
182
- if not(code.is_a?(String) and code.length == 4)
183
- raise ArgumentError, "Code must be a four-character string: #{code.inspect}"
184
- end
185
- @pack_text_as_type = code
186
- end
187
-
188
- def use_ascii_8bit
189
- # By default on Ruby 1.9+, Codecs#pack creates String instances with UTF-8
190
- # encoding and #unpack ensures all strings are UTF-8 encoded before packing
191
- # them into AEDescs of typeUTF8Text. To force the old-style behaviour where
192
- # strings are treated as byte strings containing UTF-8 data, call:
193
- #
194
- # some_application.AS_app_data.use_ascii_8bit_strings
195
- #
196
- # This will cause Strings to use the binary ASCII-8BIT encoding; as in Ruby 1.8,
197
- # the user is responsible for ensuring that strings contain UTF-8 data.
198
- @encoding_support = AEMEncodingSupport::DisableStringEncodings
199
- end
200
-
201
- def use_datetime
202
- # By default dates are unpacked as Time instances, which have limited range.
203
- # Call this method to unpack dates as DateTime instances instead.
204
- @unpack_dates_as_datetime = true
205
- end
206
-
207
- ######################################################################
208
- # Subclasses could override these to provide their own reference roots if needed
209
-
210
- App = AEMReference::App
211
- Con = AEMReference::Con
212
- Its = AEMReference::Its
213
-
214
- ######################################################################
215
- # Pack
216
-
217
- SInt32Bounds = (-2**31)..(2**31-1)
218
- SInt64Bounds = (-2**63)..(2**63-1)
219
- UInt64Bounds = (2**63)..(2**64-1)
220
-
221
- NullDesc = AE::AEDesc.new(KAE::TypeNull, '')
222
- TrueDesc = AE::AEDesc.new(KAE::TypeTrue, '')
223
- FalseDesc = AE::AEDesc.new(KAE::TypeFalse, '')
224
-
225
- ##
226
-
227
- def pack_unknown(val) # clients may override this to provide additional packers
228
- raise TypeError, "Can't pack data into an AEDesc (unsupported type): #{val.inspect}"
229
- end
230
-
231
-
232
- def pack(val) # clients may override this to replace existing packers
233
- case val
234
- when AEMReference::Query then val.AEM_pack_self(self)
235
-
236
- when Fixnum, Bignum then
237
- if SInt32Bounds === val
238
- AE::AEDesc.new(KAE::TypeSInt32, [val].pack('l'))
239
- elsif SInt64Bounds === val
240
- AE::AEDesc.new(KAE::TypeSInt64, [val].pack('q'))
241
- elsif UInt64Bounds === val
242
- pack_uint64(val)
243
- else
244
- AE::AEDesc.new(KAE::TypeFloat, [val.to_f].pack('d'))
245
- end
246
-
247
- when String then
248
- @encoding_support.pack_string(val, @pack_text_as_type)
249
-
250
- when TrueClass then TrueDesc
251
- when FalseClass then FalseDesc
252
-
253
- when Float then AE::AEDesc.new(KAE::TypeFloat, [val].pack('d'))
254
-
255
- when Time
256
- AE::AEDesc.new(KAE::TypeLongDateTime,
257
- [AE.convert_unix_seconds_to_long_date_time(val.to_i)].pack('q'))
258
-
259
- when DateTime, Date then
260
- AE::AEDesc.new(KAE::TypeLongDateTime,
261
- [AE.convert_string_to_long_date_time(val.strftime('%F %T'))].pack('q'))
262
-
263
- when Array then pack_array(val)
264
- when Hash then pack_hash(val)
265
-
266
- when MacTypes::FileBase then val.desc
267
-
268
- when TypeWrappers::AEType then
269
- AE::AEDesc.new(KAE::TypeType, Codecs.four_char_code(val.code))
270
- when TypeWrappers::AEEnum then
271
- AE::AEDesc.new(KAE::TypeEnumerated, Codecs.four_char_code(val.code))
272
- when TypeWrappers::AEProp then
273
- AE::AEDesc.new(KAE::TypeProperty, Codecs.four_char_code(val.code))
274
- when TypeWrappers::AEKey then
275
- AE::AEDesc.new(KAE::TypeKeyword, Codecs.four_char_code(val.code))
276
-
277
- when AE::AEDesc then val
278
-
279
- when NilClass then NullDesc
280
- else
281
- did_pack, desc = @unit_type_codecs.pack(val)
282
- if did_pack
283
- desc
284
- else
285
- pack_unknown(val)
286
- end
287
- end
288
- end
289
-
290
- #######
291
-
292
- def pack_uint64(val)
293
- # On 10.5+, clients could override this method to do a non-lossy conversion,
294
- # (assuming target app knows how to handle new UInt64 type):
295
- #
296
- # def pack_uint64(val)
297
- # AE::AEDesc.new(KAE::TypeUInt64, [val.to_f].pack('Q'))
298
- # end
299
- AE::AEDesc.new(KAE::TypeFloat, [val.to_f].pack('d')) # pack as 64-bit float for compatibility (lossy conversion)
300
- end
301
-
302
- def pack_array(val)
303
- lst = AE::AEDesc.new_list(false)
304
- val.each do |item|
305
- lst.put_item(0, pack(item))
306
- end
307
- return lst
308
- end
309
-
310
- def pack_hash(val)
311
- record = AE::AEDesc.new_list(true)
312
- usrf = nil
313
- val.each do | key, value |
314
- if key.is_a?(TypeWrappers::AETypeBase)
315
- if key.code == KAE::PClass # AS packs records that contain a 'class' property by coercing the packed record to that type at the end
316
- begin
317
- record = record.coerce(value.code)
318
- rescue
319
- record.put_param(key.code, pack(value))
320
- end
321
- else
322
- record.put_param(key.code, pack(value))
323
- end
324
- else
325
- if usrf == nil
326
- usrf = AE::AEDesc.new_list(false)
327
- end
328
- usrf.put_item(0, pack(key))
329
- usrf.put_item(0, pack(value))
330
- end
331
- end
332
- if usrf
333
- record.put_param(KAE::KeyASUserRecordFields, usrf)
334
- end
335
- return record
336
- end
337
-
338
- ######################################################################
339
- # Unpack
340
-
341
- def unpack_unknown(desc) # clients may override this to provide additional unpackers
342
- if desc.is_record? # if it's a record-like structure with an unknown/unsupported type then unpack it as a hash, including the original type info as a 'class' property
343
- rec = desc.coerce(KAE::TypeAERecord)
344
- rec.put_param(KAE::PClass, pack(TypeWrappers::AEType.new(desc.type)))
345
- unpack(rec)
346
- else # else return unchanged
347
- desc
348
- end
349
- end
350
-
351
-
352
- def unpack(desc) # clients may override this to replace existing unpackers
353
- return case desc.type
354
-
355
- when KAE::TypeObjectSpecifier then unpack_object_specifier(desc)
356
-
357
- when KAE::TypeSInt32 then desc.data.unpack('l')[0]
358
- when KAE::TypeIEEE64BitFloatingPoint then desc.data.unpack('d')[0]
359
-
360
- when
361
- KAE::TypeUnicodeText,
362
- KAE::TypeChar,
363
- KAE::TypeIntlText,
364
- KAE::TypeUTF16ExternalRepresentation,
365
- KAE::TypeStyledText
366
- @encoding_support.unpack_string(desc)
367
-
368
- when KAE::TypeFalse then false
369
- when KAE::TypeTrue then true
370
-
371
- when KAE::TypeLongDateTime then
372
- t = desc.data.unpack('q')[0]
373
- if @unpack_dates_as_datetime
374
- DateTime.strptime(AE.convert_long_date_time_to_string(t), '%F %T')
375
- else
376
- Time.at(AE.convert_long_date_time_to_unix_seconds(t))
377
- end
378
-
379
- when KAE::TypeAEList then unpack_aelist(desc)
380
- when KAE::TypeAERecord then unpack_aerecord(desc)
381
-
382
- when KAE::TypeAlias then MacTypes::Alias.desc(desc)
383
- when
384
- KAE::TypeFileURL,
385
- KAE::TypeFSRef,
386
- KAE::TypeFSS
387
- MacTypes::FileURL.desc(desc)
388
-
389
- when KAE::TypeType then unpack_type(desc)
390
- when KAE::TypeEnumerated then unpack_enumerated(desc)
391
- when KAE::TypeProperty then unpack_property(desc)
392
- when KAE::TypeKeyword then unpack_keyword(desc)
393
-
394
- when KAE::TypeSInt16 then desc.data.unpack('s')[0]
395
- when KAE::TypeUInt32 then desc.data.unpack('L')[0]
396
- when KAE::TypeSInt64 then desc.data.unpack('q')[0]
397
-
398
- when KAE::TypeNull then nil
399
-
400
- when KAE::TypeUTF8Text then desc.data
401
-
402
- when KAE::TypeInsertionLoc then unpack_insertion_loc(desc)
403
- when KAE::TypeCurrentContainer then unpack_current_container(desc)
404
- when KAE::TypeObjectBeingExamined then unpack_object_being_examined(desc)
405
- when KAE::TypeCompDescriptor then unpack_comp_descriptor(desc)
406
- when KAE::TypeLogicalDescriptor then unpack_logical_descriptor(desc)
407
-
408
- when KAE::TypeIEEE32BitFloatingPoint then desc.data.unpack('f')[0]
409
- when KAE::Type128BitFloatingPoint then
410
- desc.coerce(KAE::TypeIEEE64BitFloatingPoint).data.unpack('d')[0]
411
-
412
- when KAE::TypeQDPoint then desc.data.unpack('ss').reverse
413
- when KAE::TypeQDRectangle then
414
- x1, y1, x2, y2 = desc.data.unpack('ssss')
415
- [y1, x1, y2, x2]
416
- when KAE::TypeRGBColor then desc.data.unpack('SSS')
417
-
418
- when KAE::TypeVersion
419
- begin
420
- unpack(desc.coerce(KAE::TypeUnicodeText)) # supported in 10.4+
421
- rescue
422
- vers, lo = desc.data.unpack('CC')
423
- subvers, patch = lo.divmod(16)
424
- "#{vers}.#{subvers}.#{patch}"
425
- end
426
- when KAE::TypeBoolean then desc.data[0,1] != "\000"
427
-
428
- when KAE::TypeUInt16 then desc.data.unpack('S')[0] # 10.5+
429
- when KAE::TypeUInt64 then desc.data.unpack('Q')[0] # 10.5+
430
- else
431
- did_unpack, val = @unit_type_codecs.unpack(desc)
432
- if did_unpack
433
- val
434
- else
435
- unpack_unknown(desc)
436
- end
437
- end
438
- end
439
-
440
- #######
441
-
442
- def unpack_aelist(desc)
443
- lst = []
444
- desc.length().times do |i|
445
- lst.push(unpack(desc.get_item(i + 1, KAE::TypeWildCard)[1]))
446
- end
447
- return lst
448
- end
449
-
450
- def unpack_aerecord(desc)
451
- dct = {}
452
- desc.length().times do |i|
453
- key, value = desc.get_item(i + 1, KAE::TypeWildCard)
454
- if key == KAE::KeyASUserRecordFields
455
- lst = unpack_aelist(value)
456
- (lst.length / 2).times do |j|
457
- dct[lst[j * 2]] = lst[j * 2 + 1]
458
- end
459
- else
460
- dct[TypeWrappers::AEType.new(key)] = unpack(value)
461
- end
462
- end
463
- return dct
464
- end
465
-
466
- #######
467
-
468
- def unpack_type(desc)
469
- return TypeWrappers::AEType.new(Codecs.four_char_code(desc.data))
470
- end
471
-
472
- def unpack_enumerated(desc)
473
- return TypeWrappers::AEEnum.new(Codecs.four_char_code(desc.data))
474
- end
475
-
476
- def unpack_property(desc)
477
- return TypeWrappers::AEProp.new(Codecs.four_char_code(desc.data))
478
- end
479
-
480
- def unpack_keyword(desc)
481
- return TypeWrappers::AEKey.new(Codecs.four_char_code(desc.data))
482
- end
483
-
484
- #######
485
- # Lookup tables for converting enumerator, ordinal codes to aem reference method names.
486
- # Used by unpack_object_specifier, fully_unpack_object_specifier to construct aem references.
487
-
488
- AbsoluteOrdinals = {
489
- Codecs.four_char_code(KAE::KAEFirst) => 'first',
490
- Codecs.four_char_code(KAE::KAELast) => 'last',
491
- Codecs.four_char_code(KAE::KAEMiddle) => 'middle',
492
- Codecs.four_char_code(KAE::KAEAny) => 'any',
493
- }
494
-
495
- AllAbsoluteOrdinal = Codecs.four_char_code(KAE::KAEAll)
496
-
497
- RelativePositionEnums = {
498
- Codecs.four_char_code(KAE::KAEPrevious) => 'previous',
499
- Codecs.four_char_code(KAE::KAENext) => 'next',
500
- }
501
-
502
- InsertionLocEnums = {
503
- Codecs.four_char_code(KAE::KAEBefore) => 'before',
504
- Codecs.four_char_code(KAE::KAEAfter) => 'after',
505
- Codecs.four_char_code(KAE::KAEBeginning) => 'beginning',
506
- Codecs.four_char_code(KAE::KAEEnd) => 'end',
507
- }
508
-
509
- ComparisonEnums = {
510
- Codecs.four_char_code(KAE::KAEGreaterThan) => 'gt',
511
- Codecs.four_char_code(KAE::KAEGreaterThanEquals) => 'ge',
512
- Codecs.four_char_code(KAE::KAEEquals) => 'eq',
513
- Codecs.four_char_code(KAE::KAELessThan) => 'lt',
514
- Codecs.four_char_code(KAE::KAELessThanEquals) => 'le',
515
- Codecs.four_char_code(KAE::KAEBeginsWith) => 'begins_with',
516
- Codecs.four_char_code(KAE::KAEEndsWith) => 'ends_with',
517
- Codecs.four_char_code(KAE::KAEContains) => 'contains',
518
- }
519
-
520
- LogicalEnums = {
521
- Codecs.four_char_code(KAE::KAEAND) => 'and',
522
- Codecs.four_char_code(KAE::KAEOR) => 'or',
523
- Codecs.four_char_code(KAE::KAENOT) => 'not',
524
- }
525
-
526
- #######
527
-
528
- def fully_unpack_object_specifier(desc)
529
- # Recursively unpack an object specifier and all of its container descs.
530
- # (Note: Codecs#unpack_object_specifier and AEMReference::DeferredSpecifier#_real_ref will call this when needed.)
531
- case desc.type
532
- when KAE::TypeObjectSpecifier
533
- want = Codecs.four_char_code(desc.get_param(KAE::KeyAEDesiredClass, KAE::TypeType).data)
534
- key_form = Codecs.four_char_code(desc.get_param(KAE::KeyAEKeyForm, KAE::TypeEnumeration).data)
535
- key = desc.get_param(KAE::KeyAEKeyData, KAE::TypeWildCard)
536
- ref = fully_unpack_object_specifier(desc.get_param(KAE::KeyAEContainer, KAE::TypeWildCard))
537
- case key_form
538
- when KAE::FormPropertyID
539
- return ref.property(Codecs.four_char_code(key.data))
540
- when KAE::FormUserPropertyID
541
- return ref.userproperty(unpack(key))
542
- when KAE::FormRelativePosition
543
- return ref.send(RelativePositionEnums[key.data], want)
544
- else
545
- ref = ref.elements(want)
546
- case key_form
547
- when KAE::FormAbsolutePosition
548
- if key.type == KAE::TypeAbsoluteOrdinal
549
- if key.data == AllAbsoluteOrdinal
550
- return ref
551
- else
552
- return ref.send(AbsoluteOrdinals[key.data])
553
- end
554
- else
555
- return ref.by_index(unpack(key))
556
- end
557
- when KAE::FormName
558
- return ref.by_name(unpack(key))
559
- when KAE::FormUniqueID
560
- return ref.by_id(unpack(key))
561
- when KAE::FormRange
562
- return ref.by_range(
563
- unpack(key.get_param(KAE::KeyAERangeStart, KAE::TypeWildCard)),
564
- unpack(key.get_param(KAE::KeyAERangeStop, KAE::TypeWildCard)))
565
- when KAE::FormTest
566
- return ref.by_filter(unpack(key))
567
- end
568
- end
569
- raise TypeError
570
- when KAE::TypeNull then return self.class::App
571
- when KAE::TypeCurrentContainer then return self.class::Con
572
- when KAE::TypeObjectBeingExamined then return self.class::Its
573
- else
574
- return unpack(desc)
575
- end
576
- end
577
-
578
- ##
579
-
580
- def unpack_object_specifier(desc)
581
- # Shallow-unpack an object specifier, retaining the container AEDesc as-is.
582
- # (i.e. Defers full unpacking of [most] object specifiers for efficiency.)
583
- key_form = Codecs.four_char_code(desc.get_param(KAE::KeyAEKeyForm, KAE::TypeEnumeration).data)
584
- if [KAE::FormPropertyID, KAE::FormAbsolutePosition, KAE::FormName, KAE::FormUniqueID].include?(key_form)
585
- want = Codecs.four_char_code(desc.get_param(KAE::KeyAEDesiredClass, KAE::TypeType).data)
586
- key = desc.get_param(KAE::KeyAEKeyData, KAE::TypeWildCard)
587
- container = AEMReference::DeferredSpecifier.new(desc.get_param(KAE::KeyAEContainer, KAE::TypeWildCard), self)
588
- case key_form
589
- when KAE::FormPropertyID
590
- ref = AEMReference::Property.new(want, container, Codecs.four_char_code(key.data))
591
- when KAE::FormAbsolutePosition
592
- if key.type == KAE::TypeAbsoluteOrdinal
593
- if key.data == AllAbsoluteOrdinal
594
- ref = AEMReference::AllElements.new(want, container)
595
- else
596
- ref = fully_unpack_object_specifier(desc) # do a full unpack of rarely returned reference forms
597
- end
598
- else
599
- ref = AEMReference::ElementByIndex.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
600
- end
601
- when KAE::FormName
602
- ref = AEMReference::ElementByName.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
603
- when KAE::FormUniqueID
604
- ref = AEMReference::ElementByID.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
605
- end
606
- else
607
- ref = fully_unpack_object_specifier(desc) # do a full unpack of more complex, rarely returned reference forms
608
- end
609
- ref.AEM_set_desc(desc) # retain existing AEDesc for efficiency
610
- return ref
611
- end
612
-
613
-
614
- def unpack_insertion_loc(desc)
615
- return unpack_object_specifier(desc.get_param(KAE::KeyAEObject, KAE::TypeWildCard)).send(InsertionLocEnums[desc.get_param(KAE::KeyAEPosition, KAE::TypeEnumeration).data])
616
- end
617
-
618
- ##
619
-
620
- def unpack_current_container(desc)
621
- return Con
622
- end
623
-
624
- def unpack_object_being_examined(desc)
625
- return Its
626
- end
627
-
628
- ##
629
-
630
- def unpack_contains_comp_descriptor(op1, op2)
631
- # KAEContains is also used to construct 'is_in' tests, where test value is first operand and
632
- # reference being tested is second operand, so need to make sure first operand is an its-based ref;
633
- # if not, rearrange accordingly.
634
- # Since type-checking is involved, this extra hook is provided so that appscript's AppData subclass can override this method to add its own type checking
635
- if op1.is_a?(AEMReference::Query) and op1.AEM_root == AEMReference::Its
636
- return op1.contains(op2)
637
- else
638
- return op2.is_in(op1)
639
- end
640
- end
641
-
642
- def unpack_comp_descriptor(desc)
643
- operator = ComparisonEnums[desc.get_param(KAE::KeyAECompOperator, KAE::TypeEnumeration).data]
644
- op1 = unpack(desc.get_param(KAE::KeyAEObject1, KAE::TypeWildCard))
645
- op2 = unpack(desc.get_param(KAE::KeyAEObject2, KAE::TypeWildCard))
646
- if operator == 'contains'
647
- return unpack_contains_comp_descriptor(op1, op2)
648
- else
649
- return op1.send(operator, op2)
650
- end
651
- end
652
-
653
- def unpack_logical_descriptor(desc)
654
- operator = LogicalEnums[desc.get_param(KAE::KeyAELogicalOperator, KAE::TypeEnumeration).data]
655
- operands = unpack(desc.get_param(KAE::KeyAELogicalTerms, KAE::TypeAEList))
656
- return operands[0].send(operator, *operands[1, operands.length])
657
- end
658
-
136
+ # Provides pack and unpack methods for converting data between Ruby and AE types.
137
+ #
138
+ # May be subclassed to extend/alter its behaviour (e.g. the appscript layer does this).
139
+ # Conversions that are most likely to be modified (e.g. for packing and and unpacking
140
+ # references, records, types and enums) are exposed as overrideable hook methods.
141
+
142
+ extend([1].pack('s') == "\001\000" ? SmallEndianConverters : BigEndianConverters)
143
+
144
+ def initialize
145
+ @unit_type_codecs = UnitTypeCodecs.new
146
+ # Note: while typeUnicodeText is deprecated (see AEDataModel.h), it's still the
147
+ # most commonly used Unicode type so is used here for compatibility's sake.
148
+ # typeUTF8Text was initially tried, but existing applications had problems with it; i.e.
149
+ # some apps make unsafe assumptions on what to expect based on AS's behaviour.
150
+ # Once AppleScript is using typeUTF8Text/typeUTF16ExternalRepresentation
151
+ # and existing applications don't choke, this code can be similarly upgraded.
152
+ @pack_text_as_type = KAE::TypeUnicodeText
153
+ # on Ruby 1.9+, set String encoding to UTF-8
154
+ @encoding_support = AEMEncodingSupport.encoding_support
155
+ @unpack_dates_as_datetime = false
156
+ end
157
+
158
+ ######################################################################
159
+ # Compatibility options
160
+
161
+ def add_unit_types(type_defs)
162
+ # register custom unit type definitions with this Codecs instance
163
+ # e.g. Adobe apps define additional unit types (ciceros, pixels, etc.)
164
+ @unit_type_codecs.add_types(type_defs)
165
+ end
166
+
167
+ def dont_cache_unpacked_specifiers
168
+ # When unpacking object specifiers, unlike AppleScript, appscript caches
169
+ # the original AEDesc for efficiency, allowing the resulting reference to
170
+ # be re-packed much more quickly. Occasionally this causes compatibility
171
+ # problems with applications that returned subtly malformed specifiers.
172
+ # To force a Codecs object to fully unpack and repack object specifiers,
173
+ # call its dont_cache_unpacked_specifiers method.
174
+ extend(DisableObjectSpecifierCaching)
175
+ end
176
+
177
+ def pack_strings_as_type(code)
178
+ # Some older (pre-OS X) applications may require text to be passed as
179
+ # typeChar or typeIntlText rather than the usual typeUnicodeText. To force
180
+ # an AEM::Codecs object to pack strings as one of these older types, call
181
+ # its pack_strings_as_type method, specifying the type you want used instead.
182
+ if not(code.is_a?(String) and code.length == 4)
183
+ raise ArgumentError, "Code must be a four-character string: #{code.inspect}"
184
+ end
185
+ @pack_text_as_type = code
186
+ end
187
+
188
+ def use_ascii_8bit
189
+ # By default on Ruby 1.9+, Codecs#pack creates String instances with UTF-8
190
+ # encoding and #unpack ensures all strings are UTF-8 encoded before packing
191
+ # them into AEDescs of typeUTF8Text. To force the old-style behaviour where
192
+ # strings are treated as byte strings containing UTF-8 data, call:
193
+ #
194
+ # some_application.AS_app_data.use_ascii_8bit_strings
195
+ #
196
+ # This will cause Strings to use the binary ASCII-8BIT encoding; as in Ruby 1.8,
197
+ # the user is responsible for ensuring that strings contain UTF-8 data.
198
+ @encoding_support = AEMEncodingSupport::DisableStringEncodings
199
+ end
200
+
201
+ def use_datetime
202
+ # By default dates are unpacked as Time instances, which have limited range.
203
+ # Call this method to unpack dates as DateTime instances instead.
204
+ @unpack_dates_as_datetime = true
205
+ end
206
+
207
+ ######################################################################
208
+ # Subclasses could override these to provide their own reference roots if needed
209
+
210
+ App = AEMReference::App
211
+ Con = AEMReference::Con
212
+ Its = AEMReference::Its
213
+
214
+ ######################################################################
215
+ # Pack
216
+
217
+ SInt32Bounds = (-2**31)..(2**31-1)
218
+ SInt64Bounds = (-2**63)..(2**63-1)
219
+ UInt64Bounds = (2**63)..(2**64-1)
220
+
221
+ NullDesc = AE::AEDesc.new(KAE::TypeNull, '')
222
+ TrueDesc = AE::AEDesc.new(KAE::TypeTrue, '')
223
+ FalseDesc = AE::AEDesc.new(KAE::TypeFalse, '')
224
+
225
+ ##
226
+
227
+ def pack_unknown(val) # clients may override this to provide additional packers
228
+ raise TypeError, "Can't pack data into an AEDesc (unsupported type): #{val.inspect}"
229
+ end
230
+
231
+
232
+ def pack(val) # clients may override this to replace existing packers
233
+ case val
234
+ when AEMReference::Query then val.AEM_pack_self(self)
235
+
236
+ when Fixnum, Bignum then
237
+ if SInt32Bounds === val
238
+ AE::AEDesc.new(KAE::TypeSInt32, [val].pack('l'))
239
+ elsif SInt64Bounds === val
240
+ AE::AEDesc.new(KAE::TypeSInt64, [val].pack('q'))
241
+ elsif UInt64Bounds === val
242
+ pack_uint64(val)
243
+ else
244
+ AE::AEDesc.new(KAE::TypeFloat, [val.to_f].pack('d'))
245
+ end
246
+
247
+ when String then
248
+ @encoding_support.pack_string(val, @pack_text_as_type)
249
+
250
+ when TrueClass then TrueDesc
251
+ when FalseClass then FalseDesc
252
+
253
+ when Float then AE::AEDesc.new(KAE::TypeFloat, [val].pack('d'))
254
+
255
+ when Time
256
+ AE::AEDesc.new(KAE::TypeLongDateTime,
257
+ [AE.convert_unix_seconds_to_long_date_time(val.to_i)].pack('q'))
258
+
259
+ when DateTime, Date then
260
+ AE::AEDesc.new(KAE::TypeLongDateTime,
261
+ [AE.convert_string_to_long_date_time(val.strftime('%F %T'))].pack('q'))
262
+
263
+ when Array then pack_array(val)
264
+ when Hash then pack_hash(val)
265
+
266
+ when MacTypes::FileBase then val.desc
267
+
268
+ when TypeWrappers::AEType then
269
+ AE::AEDesc.new(KAE::TypeType, Codecs.four_char_code(val.code))
270
+ when TypeWrappers::AEEnum then
271
+ AE::AEDesc.new(KAE::TypeEnumerated, Codecs.four_char_code(val.code))
272
+ when TypeWrappers::AEProp then
273
+ AE::AEDesc.new(KAE::TypeProperty, Codecs.four_char_code(val.code))
274
+ when TypeWrappers::AEKey then
275
+ AE::AEDesc.new(KAE::TypeKeyword, Codecs.four_char_code(val.code))
276
+
277
+ when AE::AEDesc then val
278
+
279
+ when NilClass then NullDesc
280
+ else
281
+ did_pack, desc = @unit_type_codecs.pack(val)
282
+ if did_pack
283
+ desc
284
+ else
285
+ pack_unknown(val)
286
+ end
287
+ end
288
+ end
289
+
290
+ #######
291
+
292
+ def pack_uint64(val)
293
+ # On 10.5+, clients could override this method to do a non-lossy conversion,
294
+ # (assuming target app knows how to handle new UInt64 type):
295
+ #
296
+ # def pack_uint64(val)
297
+ # AE::AEDesc.new(KAE::TypeUInt64, [val.to_f].pack('Q'))
298
+ # end
299
+ AE::AEDesc.new(KAE::TypeFloat, [val.to_f].pack('d')) # pack as 64-bit float for compatibility (lossy conversion)
300
+ end
301
+
302
+ def pack_array(val)
303
+ lst = AE::AEDesc.new_list(false)
304
+ val.each do |item|
305
+ lst.put_item(0, pack(item))
306
+ end
307
+ return lst
308
+ end
309
+
310
+ def pack_hash(val)
311
+ record = AE::AEDesc.new_list(true)
312
+ usrf = nil
313
+ val.each do | key, value |
314
+ if key.is_a?(TypeWrappers::AETypeBase)
315
+ if key.code == KAE::PClass # AS packs records that contain a 'class' property by coercing the packed record to that type at the end
316
+ begin
317
+ record = record.coerce(value.code)
318
+ rescue
319
+ record.put_param(key.code, pack(value))
320
+ end
321
+ else
322
+ record.put_param(key.code, pack(value))
323
+ end
324
+ else
325
+ if usrf == nil
326
+ usrf = AE::AEDesc.new_list(false)
327
+ end
328
+ usrf.put_item(0, pack(key))
329
+ usrf.put_item(0, pack(value))
330
+ end
331
+ end
332
+ if usrf
333
+ record.put_param(KAE::KeyASUserRecordFields, usrf)
334
+ end
335
+ return record
336
+ end
337
+
338
+ ######################################################################
339
+ # Unpack
340
+
341
+ def unpack_unknown(desc) # clients may override this to provide additional unpackers
342
+ if desc.is_record? # if it's a record-like structure with an unknown/unsupported type then unpack it as a hash, including the original type info as a 'class' property
343
+ rec = desc.coerce(KAE::TypeAERecord)
344
+ rec.put_param(KAE::PClass, pack(TypeWrappers::AEType.new(desc.type)))
345
+ unpack(rec)
346
+ else # else return unchanged
347
+ desc
348
+ end
349
+ end
350
+
351
+
352
+ def unpack(desc) # clients may override this to replace existing unpackers
353
+ return case desc.type
354
+
355
+ when KAE::TypeObjectSpecifier then unpack_object_specifier(desc)
356
+
357
+ when KAE::TypeSInt32 then desc.data.unpack('l')[0]
358
+ when KAE::TypeIEEE64BitFloatingPoint then desc.data.unpack('d')[0]
359
+
360
+ when
361
+ KAE::TypeUnicodeText,
362
+ KAE::TypeChar,
363
+ KAE::TypeIntlText,
364
+ KAE::TypeUTF16ExternalRepresentation,
365
+ KAE::TypeStyledText
366
+ @encoding_support.unpack_string(desc)
367
+
368
+ when KAE::TypeFalse then false
369
+ when KAE::TypeTrue then true
370
+
371
+ when KAE::TypeLongDateTime then
372
+ t = desc.data.unpack('q')[0]
373
+ if @unpack_dates_as_datetime
374
+ DateTime.strptime(AE.convert_long_date_time_to_string(t), '%F %T')
375
+ else
376
+ Time.at(AE.convert_long_date_time_to_unix_seconds(t))
377
+ end
378
+
379
+ when KAE::TypeAEList then unpack_aelist(desc)
380
+ when KAE::TypeAERecord then unpack_aerecord(desc)
381
+
382
+ when KAE::TypeAlias then MacTypes::Alias.desc(desc)
383
+ when
384
+ KAE::TypeFileURL,
385
+ KAE::TypeFSRef,
386
+ KAE::TypeFSS
387
+ MacTypes::FileURL.desc(desc)
388
+
389
+ when KAE::TypeType then unpack_type(desc)
390
+ when KAE::TypeEnumerated then unpack_enumerated(desc)
391
+ when KAE::TypeProperty then unpack_property(desc)
392
+ when KAE::TypeKeyword then unpack_keyword(desc)
393
+
394
+ when KAE::TypeSInt16 then desc.data.unpack('s')[0]
395
+ when KAE::TypeUInt32 then desc.data.unpack('L')[0]
396
+ when KAE::TypeSInt64 then desc.data.unpack('q')[0]
397
+
398
+ when KAE::TypeNull then nil
399
+
400
+ when KAE::TypeUTF8Text then desc.data
401
+
402
+ when KAE::TypeInsertionLoc then unpack_insertion_loc(desc)
403
+ when KAE::TypeCurrentContainer then unpack_current_container(desc)
404
+ when KAE::TypeObjectBeingExamined then unpack_object_being_examined(desc)
405
+ when KAE::TypeCompDescriptor then unpack_comp_descriptor(desc)
406
+ when KAE::TypeLogicalDescriptor then unpack_logical_descriptor(desc)
407
+
408
+ when KAE::TypeIEEE32BitFloatingPoint then desc.data.unpack('f')[0]
409
+ when KAE::Type128BitFloatingPoint then
410
+ desc.coerce(KAE::TypeIEEE64BitFloatingPoint).data.unpack('d')[0]
411
+
412
+ when KAE::TypeQDPoint then desc.data.unpack('ss').reverse
413
+ when KAE::TypeQDRectangle then
414
+ x1, y1, x2, y2 = desc.data.unpack('ssss')
415
+ [y1, x1, y2, x2]
416
+ when KAE::TypeRGBColor then desc.data.unpack('SSS')
417
+
418
+ when KAE::TypeVersion
419
+ begin
420
+ unpack(desc.coerce(KAE::TypeUnicodeText)) # supported in 10.4+
421
+ rescue
422
+ vers, lo = desc.data.unpack('CC')
423
+ subvers, patch = lo.divmod(16)
424
+ "#{vers}.#{subvers}.#{patch}"
425
+ end
426
+ when KAE::TypeBoolean then desc.data[0,1] != "\000"
427
+
428
+ when KAE::TypeUInt16 then desc.data.unpack('S')[0] # 10.5+
429
+ when KAE::TypeUInt64 then desc.data.unpack('Q')[0] # 10.5+
430
+ else
431
+ did_unpack, val = @unit_type_codecs.unpack(desc)
432
+ if did_unpack
433
+ val
434
+ else
435
+ unpack_unknown(desc)
436
+ end
437
+ end
438
+ end
439
+
440
+ #######
441
+
442
+ def unpack_aelist(desc)
443
+ lst = []
444
+ desc.length().times do |i|
445
+ lst.push(unpack(desc.get_item(i + 1, KAE::TypeWildCard)[1]))
446
+ end
447
+ return lst
448
+ end
449
+
450
+ def unpack_aerecord(desc)
451
+ dct = {}
452
+ desc.length().times do |i|
453
+ key, value = desc.get_item(i + 1, KAE::TypeWildCard)
454
+ if key == KAE::KeyASUserRecordFields
455
+ lst = unpack_aelist(value)
456
+ (lst.length / 2).times do |j|
457
+ dct[lst[j * 2]] = lst[j * 2 + 1]
458
+ end
459
+ else
460
+ dct[TypeWrappers::AEType.new(key)] = unpack(value)
461
+ end
462
+ end
463
+ return dct
464
+ end
465
+
466
+ #######
467
+
468
+ def unpack_type(desc)
469
+ return TypeWrappers::AEType.new(Codecs.four_char_code(desc.data))
470
+ end
471
+
472
+ def unpack_enumerated(desc)
473
+ return TypeWrappers::AEEnum.new(Codecs.four_char_code(desc.data))
474
+ end
475
+
476
+ def unpack_property(desc)
477
+ return TypeWrappers::AEProp.new(Codecs.four_char_code(desc.data))
478
+ end
479
+
480
+ def unpack_keyword(desc)
481
+ return TypeWrappers::AEKey.new(Codecs.four_char_code(desc.data))
482
+ end
483
+
484
+ #######
485
+ # Lookup tables for converting enumerator, ordinal codes to aem reference method names.
486
+ # Used by unpack_object_specifier, fully_unpack_object_specifier to construct aem references.
487
+
488
+ AbsoluteOrdinals = {
489
+ Codecs.four_char_code(KAE::KAEFirst) => 'first',
490
+ Codecs.four_char_code(KAE::KAELast) => 'last',
491
+ Codecs.four_char_code(KAE::KAEMiddle) => 'middle',
492
+ Codecs.four_char_code(KAE::KAEAny) => 'any',
493
+ }
494
+
495
+ AllAbsoluteOrdinal = Codecs.four_char_code(KAE::KAEAll)
496
+
497
+ RelativePositionEnums = {
498
+ Codecs.four_char_code(KAE::KAEPrevious) => 'previous',
499
+ Codecs.four_char_code(KAE::KAENext) => 'next',
500
+ }
501
+
502
+ InsertionLocEnums = {
503
+ Codecs.four_char_code(KAE::KAEBefore) => 'before',
504
+ Codecs.four_char_code(KAE::KAEAfter) => 'after',
505
+ Codecs.four_char_code(KAE::KAEBeginning) => 'beginning',
506
+ Codecs.four_char_code(KAE::KAEEnd) => 'end',
507
+ }
508
+
509
+ ComparisonEnums = {
510
+ Codecs.four_char_code(KAE::KAEGreaterThan) => 'gt',
511
+ Codecs.four_char_code(KAE::KAEGreaterThanEquals) => 'ge',
512
+ Codecs.four_char_code(KAE::KAEEquals) => 'eq',
513
+ Codecs.four_char_code(KAE::KAELessThan) => 'lt',
514
+ Codecs.four_char_code(KAE::KAELessThanEquals) => 'le',
515
+ Codecs.four_char_code(KAE::KAEBeginsWith) => 'begins_with',
516
+ Codecs.four_char_code(KAE::KAEEndsWith) => 'ends_with',
517
+ Codecs.four_char_code(KAE::KAEContains) => 'contains',
518
+ }
519
+
520
+ LogicalEnums = {
521
+ Codecs.four_char_code(KAE::KAEAND) => 'and',
522
+ Codecs.four_char_code(KAE::KAEOR) => 'or',
523
+ Codecs.four_char_code(KAE::KAENOT) => 'not',
524
+ }
525
+
526
+ #######
527
+
528
+ def fully_unpack_object_specifier(desc)
529
+ # Recursively unpack an object specifier and all of its container descs.
530
+ # (Note: Codecs#unpack_object_specifier and AEMReference::DeferredSpecifier#_real_ref will call this when needed.)
531
+ case desc.type
532
+ when KAE::TypeObjectSpecifier
533
+ want = Codecs.four_char_code(desc.get_param(KAE::KeyAEDesiredClass, KAE::TypeType).data)
534
+ key_form = Codecs.four_char_code(desc.get_param(KAE::KeyAEKeyForm, KAE::TypeEnumeration).data)
535
+ key = desc.get_param(KAE::KeyAEKeyData, KAE::TypeWildCard)
536
+ ref = fully_unpack_object_specifier(desc.get_param(KAE::KeyAEContainer, KAE::TypeWildCard))
537
+ case key_form
538
+ when KAE::FormPropertyID
539
+ return ref.property(Codecs.four_char_code(key.data))
540
+ when KAE::FormUserPropertyID
541
+ return ref.userproperty(unpack(key))
542
+ when KAE::FormRelativePosition
543
+ return ref.send(RelativePositionEnums[key.data], want)
544
+ else
545
+ ref = ref.elements(want)
546
+ case key_form
547
+ when KAE::FormAbsolutePosition
548
+ if key.type == KAE::TypeAbsoluteOrdinal
549
+ if key.data == AllAbsoluteOrdinal
550
+ return ref
551
+ else
552
+ return ref.send(AbsoluteOrdinals[key.data])
553
+ end
554
+ else
555
+ return ref.by_index(unpack(key))
556
+ end
557
+ when KAE::FormName
558
+ return ref.by_name(unpack(key))
559
+ when KAE::FormUniqueID
560
+ return ref.by_id(unpack(key))
561
+ when KAE::FormRange
562
+ return ref.by_range(
563
+ unpack(key.get_param(KAE::KeyAERangeStart, KAE::TypeWildCard)),
564
+ unpack(key.get_param(KAE::KeyAERangeStop, KAE::TypeWildCard)))
565
+ when KAE::FormTest
566
+ return ref.by_filter(unpack(key))
567
+ end
568
+ end
569
+ raise TypeError
570
+ when KAE::TypeNull then return self.class::App
571
+ when KAE::TypeCurrentContainer then return self.class::Con
572
+ when KAE::TypeObjectBeingExamined then return self.class::Its
573
+ else
574
+ return unpack(desc)
575
+ end
576
+ end
577
+
578
+ ##
579
+
580
+ def unpack_object_specifier(desc)
581
+ # Shallow-unpack an object specifier, retaining the container AEDesc as-is.
582
+ # (i.e. Defers full unpacking of [most] object specifiers for efficiency.)
583
+ key_form = Codecs.four_char_code(desc.get_param(KAE::KeyAEKeyForm, KAE::TypeEnumeration).data)
584
+ if [KAE::FormPropertyID, KAE::FormAbsolutePosition, KAE::FormName, KAE::FormUniqueID].include?(key_form)
585
+ want = Codecs.four_char_code(desc.get_param(KAE::KeyAEDesiredClass, KAE::TypeType).data)
586
+ key = desc.get_param(KAE::KeyAEKeyData, KAE::TypeWildCard)
587
+ container = AEMReference::DeferredSpecifier.new(desc.get_param(KAE::KeyAEContainer, KAE::TypeWildCard), self)
588
+ case key_form
589
+ when KAE::FormPropertyID
590
+ ref = AEMReference::Property.new(want, container, Codecs.four_char_code(key.data))
591
+ when KAE::FormAbsolutePosition
592
+ if key.type == KAE::TypeAbsoluteOrdinal
593
+ if key.data == AllAbsoluteOrdinal
594
+ ref = AEMReference::AllElements.new(want, container)
595
+ else
596
+ ref = fully_unpack_object_specifier(desc) # do a full unpack of rarely returned reference forms
597
+ end
598
+ else
599
+ ref = AEMReference::ElementByIndex.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
600
+ end
601
+ when KAE::FormName
602
+ ref = AEMReference::ElementByName.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
603
+ when KAE::FormUniqueID
604
+ ref = AEMReference::ElementByID.new(want, AEMReference::UnkeyedElements.new(want, container), unpack(key))
605
+ end
606
+ else
607
+ ref = fully_unpack_object_specifier(desc) # do a full unpack of more complex, rarely returned reference forms
608
+ end
609
+ ref.AEM_set_desc(desc) # retain existing AEDesc for efficiency
610
+ return ref
611
+ end
612
+
613
+
614
+ def unpack_insertion_loc(desc)
615
+ return unpack_object_specifier(desc.get_param(KAE::KeyAEObject, KAE::TypeWildCard)).send(InsertionLocEnums[desc.get_param(KAE::KeyAEPosition, KAE::TypeEnumeration).data])
616
+ end
617
+
618
+ ##
619
+
620
+ def unpack_current_container(desc)
621
+ return Con
622
+ end
623
+
624
+ def unpack_object_being_examined(desc)
625
+ return Its
626
+ end
627
+
628
+ ##
629
+
630
+ def unpack_contains_comp_descriptor(op1, op2)
631
+ # KAEContains is also used to construct 'is_in' tests, where test value is first operand and
632
+ # reference being tested is second operand, so need to make sure first operand is an its-based ref;
633
+ # if not, rearrange accordingly.
634
+ # Since type-checking is involved, this extra hook is provided so that appscript's AppData subclass can override this method to add its own type checking
635
+ if op1.is_a?(AEMReference::Query) and op1.AEM_root == AEMReference::Its
636
+ return op1.contains(op2)
637
+ else
638
+ return op2.is_in(op1)
639
+ end
640
+ end
641
+
642
+ def unpack_comp_descriptor(desc)
643
+ operator = ComparisonEnums[desc.get_param(KAE::KeyAECompOperator, KAE::TypeEnumeration).data]
644
+ op1 = unpack(desc.get_param(KAE::KeyAEObject1, KAE::TypeWildCard))
645
+ op2 = unpack(desc.get_param(KAE::KeyAEObject2, KAE::TypeWildCard))
646
+ if operator == 'contains'
647
+ return unpack_contains_comp_descriptor(op1, op2)
648
+ else
649
+ return op1.send(operator, op2)
650
+ end
651
+ end
652
+
653
+ def unpack_logical_descriptor(desc)
654
+ operator = LogicalEnums[desc.get_param(KAE::KeyAELogicalOperator, KAE::TypeEnumeration).data]
655
+ operands = unpack(desc.get_param(KAE::KeyAELogicalTerms, KAE::TypeAEList))
656
+ return operands[0].send(operator, *operands[1, operands.length])
657
+ end
658
+
659
659
  end
660
660
 
661
661
  DefaultCodecs = Codecs.new
662
-