rb-scpt 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-