keeguon-ruby-ole 1.2.11.7

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.
@@ -0,0 +1,8 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ module Ole # :nodoc:
4
+ class Storage
5
+ VERSION = '1.2.11.7'
6
+ end
7
+ end
8
+
@@ -0,0 +1,248 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ #
4
+ # A file with general support functions used by most files in the project.
5
+ #
6
+ # These are the only methods added to other classes.
7
+ #
8
+
9
+ require 'logger'
10
+ require 'stringio'
11
+ require 'enumerator'
12
+
13
+ class String # :nodoc:
14
+ def each_chunk size
15
+ (length / size.to_f).ceil.times { |i| yield self[i * size, size] }
16
+ end
17
+ end
18
+
19
+ class File # :nodoc:
20
+ # for interface consistency with StringIO etc (rather than adding #stat
21
+ # to them). used by RangesIO.
22
+ unless File.method_defined?(:size) or (RUBY_PLATFORM =~ /java/).nil?
23
+ def size
24
+ stat.size
25
+ end
26
+ end
27
+ end
28
+
29
+ class Symbol # :nodoc:
30
+ unless Symbol.method_defined?(:to_proc)
31
+ def to_proc
32
+ Proc.new { |*args| args.shift.__send__(self, *args) }
33
+ end
34
+ end
35
+ end
36
+
37
+ module Enumerable # :nodoc:
38
+ unless [].respond_to? :group_by
39
+ # 1.9 backport
40
+ def group_by
41
+ hash = Hash.new { |h, key| h[key] = [] }
42
+ each { |item| hash[yield(item)] << item }
43
+ hash
44
+ end
45
+ end
46
+
47
+ unless [].respond_to? :sum
48
+ def sum initial=0
49
+ inject(initial) { |a, b| a + b }
50
+ end
51
+ end
52
+ end
53
+
54
+ # move to support?
55
+ class IO # :nodoc:
56
+ # Copy data from IO-like object +src+, to +dst+
57
+ def self.copy src, dst
58
+ until src.eof?
59
+ buf = src.read(4096)
60
+ dst.write buf
61
+ end
62
+ end
63
+ end
64
+
65
+ class Logger # :nodoc:
66
+ # A helper method for creating a +Logger+ which produce call stack
67
+ # in their output
68
+ def self.new_with_callstack logdev=STDERR
69
+ log = Logger.new logdev
70
+ log.level = WARN
71
+ log.formatter = proc do |severity, time, progname, msg|
72
+ # find where we were called from, in our code
73
+ callstack = caller.dup
74
+ callstack.shift while callstack.first =~ /\/logger\.rb:\d+:in/
75
+ from = callstack.first.sub(/:in `(.*?)'/, ":\\1")
76
+ "[%s %s]\n%-7s%s\n" % [time.strftime('%H:%M:%S'), from, severity, msg.to_s]
77
+ end
78
+ log
79
+ end
80
+ end
81
+
82
+ # Include this module into a class that defines #each_child. It should
83
+ # maybe use #each instead, but its easier to be more specific, and use
84
+ # an alias.
85
+ #
86
+ # I don't want to force the class to cache children (eg where children
87
+ # are loaded on request in pst), because that forces the whole tree to
88
+ # be loaded. So, the methods should only call #each_child once, and
89
+ # breadth first iteration holds its own copy of the children around.
90
+ #
91
+ # Main methods are #recursive, and #to_tree
92
+ module RecursivelyEnumerable # :nodoc:
93
+ def each_recursive_depth_first(&block)
94
+ each_child do |child|
95
+ yield child
96
+ if child.respond_to? :each_recursive_depth_first
97
+ child.each_recursive_depth_first(&block)
98
+ end
99
+ end
100
+ end
101
+
102
+ # don't think this is actually a proper breadth first recursion. only first
103
+ # level is breadth first.
104
+ def each_recursive_breadth_first(&block)
105
+ children = []
106
+ each_child do |child|
107
+ children << child if child.respond_to? :each_recursive_breadth_first
108
+ yield child
109
+ end
110
+ children.each { |child| child.each_recursive_breadth_first(&block) }
111
+ end
112
+
113
+ def each_recursive mode=:depth_first, &block
114
+ # we always actually yield ourself (the tree root) before recursing
115
+ yield self
116
+ send "each_recursive_#{mode}", &block
117
+ end
118
+
119
+ # the idea of this function, is to allow use of regular Enumerable methods
120
+ # in a recursive fashion. eg:
121
+ #
122
+ # # just looks at top level children
123
+ # root.find { |child| child.some_condition? }
124
+ # # recurse into all children getting non-folders, breadth first
125
+ # root.recursive(:breadth_first).select { |child| !child.folder? }
126
+ # # just get everything
127
+ # items = root.recursive.to_a
128
+ #
129
+ def recursive mode=:depth_first
130
+ to_enum(:each_recursive, mode)
131
+ end
132
+
133
+ # streams a "tree" form of the recursively enumerable structure to +io+, or
134
+ # return a string form instead if +io+ is not specified.
135
+ #
136
+ # mostly a debugging aid. can specify a different block which will be called
137
+ # to provide the string form for each node.
138
+ def to_tree io='', &inspect
139
+ inspect ||= :inspect.to_proc
140
+ io << "- #{inspect[self]}\n"
141
+ recurse = proc do |node, prefix|
142
+ child = nil
143
+ node.each_child do |next_child|
144
+ if child
145
+ io << "#{prefix}|- #{inspect[child]}\n"
146
+ recurse.call child, prefix + '| '
147
+ end
148
+ child = next_child
149
+ end if node.respond_to?(:each_child)
150
+ if child
151
+ io << "#{prefix}\\- #{inspect[child]}\n"
152
+ recurse.call child, prefix + ' '
153
+ end
154
+ end
155
+ recurse.call self, ' '
156
+ io
157
+ end
158
+ end
159
+
160
+ module Ole
161
+ class IOMode
162
+ # ruby 1.9 defines binary as 0, which isn't very helpful.
163
+ # its 4 in rubinius. no longer using
164
+ #
165
+ # BINARY = 0x4 unless defined?(BINARY)
166
+ #
167
+ # for that reason, have my own constants module here
168
+ module Constants
169
+ include File::Constants
170
+ BINARY = 0x4
171
+ end
172
+
173
+ include Constants
174
+ NAMES = %w[rdonly wronly rdwr creat trunc append binary]
175
+
176
+ # nabbed from rubinius, and modified
177
+ def self.parse_mode mode
178
+ ret = 0
179
+
180
+ case mode[0, 1]
181
+ when 'r'; ret |= RDONLY
182
+ when 'w'; ret |= WRONLY | CREAT | TRUNC
183
+ when 'a'; ret |= WRONLY | CREAT | APPEND
184
+ else raise ArgumentError, "illegal access mode #{mode}"
185
+ end
186
+
187
+ (1...mode.length).each do |i|
188
+ case mode[i, 1]
189
+ when '+'; ret = (ret & ~(RDONLY | WRONLY)) | RDWR
190
+ when 'b'; ret |= BINARY
191
+ else raise ArgumentError, "illegal access mode #{mode}"
192
+ end
193
+ end
194
+
195
+ ret
196
+ end
197
+
198
+ attr_reader :flags
199
+ def initialize flags
200
+ flags = self.class.parse_mode flags.to_str if flags.respond_to? :to_str
201
+ raise ArgumentError, "invalid flags - #{flags.inspect}" unless Fixnum === flags
202
+ @flags = flags
203
+ end
204
+
205
+ def writeable?
206
+ #(@flags & RDONLY) == 0
207
+ (@flags & 0x3) != RDONLY
208
+ end
209
+
210
+ def readable?
211
+ (@flags & WRONLY) == 0
212
+ end
213
+
214
+ def truncate?
215
+ (@flags & TRUNC) != 0
216
+ end
217
+
218
+ def append?
219
+ (@flags & APPEND) != 0
220
+ end
221
+
222
+ def create?
223
+ (@flags & CREAT) != 0
224
+ end
225
+
226
+ def binary?
227
+ (@flags & BINARY) != 0
228
+ end
229
+
230
+ =begin
231
+ # revisit this
232
+ def apply io
233
+ if truncate?
234
+ io.truncate 0
235
+ elsif append?
236
+ io.seek IO::SEEK_END, 0
237
+ end
238
+ end
239
+ =end
240
+
241
+ def inspect
242
+ names = NAMES.map { |name| name if (flags & IOMode.const_get(name.upcase)) != 0 }
243
+ names.unshift 'rdonly' if (flags & 0x3) == 0
244
+ "#<#{self.class} #{names.compact * '|'}>"
245
+ end
246
+ end
247
+ end
248
+
data/lib/ole/types.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'ole/types/base'
2
+ require 'ole/types/property_set'
@@ -0,0 +1,314 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'date'
4
+
5
+ require 'ole/base'
6
+
7
+ module Ole # :nodoc:
8
+ #
9
+ # The Types module contains all the serialization and deserialization code for standard ole
10
+ # types.
11
+ #
12
+ # It also defines all the variant type constants, and symbolic names.
13
+ #
14
+ module Types
15
+ # for anything that we don't have serialization code for
16
+ class Data < String
17
+ def self.load str
18
+ new str
19
+ end
20
+
21
+ def self.dump str
22
+ str.to_s
23
+ end
24
+ end
25
+
26
+ class Lpstr < String
27
+ def self.load str
28
+ # not sure if its always there, but there is often a trailing
29
+ # null byte.
30
+ new str.chomp(0.chr)
31
+ end
32
+
33
+ def self.dump str
34
+ # do i need to append the null byte?
35
+ str.to_s
36
+ end
37
+ end
38
+
39
+ if ''.respond_to? :encode
40
+ # NOTE: only here in the interim to preserve behaviour of
41
+ # FROM/TO_UTF16 constants for ruby-msg.
42
+ class Iconv # :nodoc:
43
+ def initialize(to, from)
44
+ @to, @from = to, from
45
+ end
46
+
47
+ def iconv(str)
48
+ str.encode(@to, @from)
49
+ end
50
+ end
51
+
52
+ # for VT_LPWSTR
53
+ class Lpwstr < String
54
+ FROM_UTF16 = Iconv.new 'utf-8', 'utf-16le'
55
+ TO_UTF16 = Iconv.new 'utf-16le', 'utf-8'
56
+
57
+ def self.load str
58
+ new str.encode(Encoding::UTF_8, Encoding::UTF_16LE).chomp(0.chr)
59
+ end
60
+
61
+ def self.dump str
62
+ # need to append nulls?
63
+ data = str.encode(Encoding::UTF_16LE)
64
+ # not sure if this is the recommended way to do it, but I want to treat
65
+ # the resulting utf16 data as regular bytes, not characters.
66
+ data.force_encoding Encoding::ASCII_8BIT
67
+ data
68
+ end
69
+ end
70
+ else
71
+ require 'iconv'
72
+
73
+ # for VT_LPWSTR
74
+ class Lpwstr < String
75
+ FROM_UTF16 = Iconv.new 'utf-8', 'utf-16le'
76
+ TO_UTF16 = Iconv.new 'utf-16le', 'utf-8'
77
+
78
+ def self.load str
79
+ new FROM_UTF16.iconv(str).chomp(0.chr)
80
+ end
81
+
82
+ def self.dump str
83
+ # need to append nulls?
84
+ TO_UTF16.iconv str
85
+ end
86
+ end
87
+ end
88
+
89
+ # for VT_FILETIME
90
+ class FileTime < DateTime
91
+ SIZE = 8
92
+
93
+ # DateTime.new is slow... faster version for FileTime
94
+ def self.new year, month, day, hour=0, min=0, sec=0
95
+ # DateTime will remove leap and leap-leap seconds
96
+ sec = 59 if sec > 59
97
+ if month <= 2
98
+ month += 12
99
+ year -= 1
100
+ end
101
+ y = year + 4800
102
+ m = month - 3
103
+ jd = day + (153 * m + 2).div(5) + 365 * y + y.div(4) - y.div(100) + y.div(400) - 32045
104
+ fr = hour / 24.0 + min / 1440.0 + sec / 86400.0
105
+ # new! was actually new0 in older versions of ruby (<=1.8.4?)
106
+ # see issue #4.
107
+ msg = respond_to?(:new!) ? :new! : :new0
108
+ send msg, jd + fr - 0.5, 0, ITALY
109
+ end if respond_to?(:new!) || respond_to?(:new0)
110
+
111
+ def self.from_time time
112
+ new(*time.to_a[0, 6].reverse)
113
+ end
114
+
115
+ def self.now
116
+ from_time Time.now
117
+ end
118
+
119
+ EPOCH = new 1601, 1, 1
120
+
121
+ #def initialize year, month, day, hour, min, sec
122
+
123
+ # Create a +DateTime+ object from a struct +FILETIME+
124
+ # (http://msdn2.microsoft.com/en-us/library/ms724284.aspx).
125
+ #
126
+ # Converts +str+ to two 32 bit time values, comprising the high and low 32 bits of
127
+ # the 100's of nanoseconds since 1st january 1601 (Epoch).
128
+ def self.load str
129
+ low, high = str.to_s.unpack 'V2'
130
+ # we ignore these, without even warning about it
131
+ return nil if low == 0 and high == 0
132
+ # the + 0.00001 here stinks a bit...
133
+ seconds = (high * (1 << 32) + low) / 1e7 + 0.00001
134
+ obj = EPOCH + seconds / 86400 rescue return
135
+ # work around home_run not preserving derived class
136
+ obj = new! obj.jd + obj.day_fraction - 0.5, 0, ITALY unless FileTime === obj
137
+ obj
138
+ end
139
+
140
+ # +time+ should be able to be either a Time, Date, or DateTime.
141
+ def self.dump time
142
+ return 0.chr * SIZE unless time
143
+ # convert whatever is given to be a datetime, to handle the large range
144
+ case time
145
+ when Date # this includes DateTime & FileTime
146
+ when Time
147
+ time = from_time time
148
+ else
149
+ raise ArgumentError, 'unknown time argument - %p' % [time]
150
+ end
151
+ # round to milliseconds (throwing away nanosecond precision) to
152
+ # compensate for using Float-based DateTime
153
+ nanoseconds = ((time - EPOCH).to_f * 864000000).round * 1000
154
+ high, low = nanoseconds.divmod 1 << 32
155
+ [low, high].pack 'V2'
156
+ end
157
+
158
+ def inspect
159
+ "#<#{self.class} #{to_s}>"
160
+ end
161
+ end
162
+
163
+ # for VT_CLSID
164
+ # Unlike most of the other conversions, the Guid's are serialized/deserialized by actually
165
+ # doing nothing! (eg, _load & _dump are null ops)
166
+ # Rather, its just a string with a different inspect string, and it includes a
167
+ # helper method for creating a Guid from that readable form (#format).
168
+ class Clsid < String
169
+ SIZE = 16
170
+ PACK = 'V v v CC C6'
171
+
172
+ def self.load str
173
+ new str.to_s
174
+ end
175
+
176
+ def self.dump guid
177
+ return 0.chr * SIZE unless guid
178
+ # allow use of plain strings in place of guids.
179
+ guid['-'] ? parse(guid) : guid
180
+ end
181
+
182
+ def self.parse str
183
+ vals = str.scan(/[a-f\d]+/i).map(&:hex)
184
+ if vals.length == 5
185
+ # this is pretty ugly
186
+ vals[3] = ('%04x' % vals[3]).scan(/../).map(&:hex)
187
+ vals[4] = ('%012x' % vals[4]).scan(/../).map(&:hex)
188
+ guid = new vals.flatten.pack(PACK)
189
+ return guid if guid.format.delete('{}') == str.downcase.delete('{}')
190
+ end
191
+ raise ArgumentError, 'invalid guid - %p' % str
192
+ end
193
+
194
+ def format
195
+ "%08x-%04x-%04x-%02x%02x-#{'%02x' * 6}" % unpack(PACK)
196
+ end
197
+
198
+ def inspect
199
+ "#<#{self.class}:{#{format}}>"
200
+ end
201
+ end
202
+
203
+ #
204
+ # The OLE variant types, extracted from
205
+ # http://www.marin.clara.net/COM/variant_type_definitions.htm.
206
+ #
207
+ # A subset is also in WIN32OLE::VARIANT, but its not cross platform (obviously).
208
+ #
209
+ # Use like:
210
+ #
211
+ # p Ole::Types::Variant::NAMES[0x001f] => 'VT_LPWSTR'
212
+ # p Ole::Types::VT_DATE # => 7
213
+ #
214
+ # The serialization / deserialization functions should be fixed to make it easier
215
+ # to work with. like
216
+ #
217
+ # Ole::Types.from_str(VT_DATE, data) # and
218
+ # Ole::Types.to_str(VT_DATE, data)
219
+ #
220
+ # Or similar, rather than having to do VT_* <=> ad hoc class name etc as it is
221
+ # currently.
222
+ #
223
+ module Variant
224
+ NAMES = {
225
+ 0x0000 => 'VT_EMPTY',
226
+ 0x0001 => 'VT_NULL',
227
+ 0x0002 => 'VT_I2',
228
+ 0x0003 => 'VT_I4',
229
+ 0x0004 => 'VT_R4',
230
+ 0x0005 => 'VT_R8',
231
+ 0x0006 => 'VT_CY',
232
+ 0x0007 => 'VT_DATE',
233
+ 0x0008 => 'VT_BSTR',
234
+ 0x0009 => 'VT_DISPATCH',
235
+ 0x000a => 'VT_ERROR',
236
+ 0x000b => 'VT_BOOL',
237
+ 0x000c => 'VT_VARIANT',
238
+ 0x000d => 'VT_UNKNOWN',
239
+ 0x000e => 'VT_DECIMAL',
240
+ 0x0010 => 'VT_I1',
241
+ 0x0011 => 'VT_UI1',
242
+ 0x0012 => 'VT_UI2',
243
+ 0x0013 => 'VT_UI4',
244
+ 0x0014 => 'VT_I8',
245
+ 0x0015 => 'VT_UI8',
246
+ 0x0016 => 'VT_INT',
247
+ 0x0017 => 'VT_UINT',
248
+ 0x0018 => 'VT_VOID',
249
+ 0x0019 => 'VT_HRESULT',
250
+ 0x001a => 'VT_PTR',
251
+ 0x001b => 'VT_SAFEARRAY',
252
+ 0x001c => 'VT_CARRAY',
253
+ 0x001d => 'VT_USERDEFINED',
254
+ 0x001e => 'VT_LPSTR',
255
+ 0x001f => 'VT_LPWSTR',
256
+ 0x0040 => 'VT_FILETIME',
257
+ 0x0041 => 'VT_BLOB',
258
+ 0x0042 => 'VT_STREAM',
259
+ 0x0043 => 'VT_STORAGE',
260
+ 0x0044 => 'VT_STREAMED_OBJECT',
261
+ 0x0045 => 'VT_STORED_OBJECT',
262
+ 0x0046 => 'VT_BLOB_OBJECT',
263
+ 0x0047 => 'VT_CF',
264
+ 0x0048 => 'VT_CLSID',
265
+ 0x0fff => 'VT_ILLEGALMASKED',
266
+ 0x0fff => 'VT_TYPEMASK',
267
+ 0x1000 => 'VT_VECTOR',
268
+ 0x2000 => 'VT_ARRAY',
269
+ 0x4000 => 'VT_BYREF',
270
+ 0x8000 => 'VT_RESERVED',
271
+ 0xffff => 'VT_ILLEGAL'
272
+ }
273
+
274
+ CLASS_MAP = {
275
+ # haven't seen one of these. wonder if its same as FILETIME?
276
+ #'VT_DATE' => ?,
277
+ 'VT_LPSTR' => Lpstr,
278
+ 'VT_LPWSTR' => Lpwstr,
279
+ 'VT_FILETIME' => FileTime,
280
+ 'VT_CLSID' => Clsid
281
+ }
282
+
283
+ module Constants
284
+ NAMES.each { |num, name| const_set name, num }
285
+ end
286
+
287
+ def self.load type, str
288
+ type = NAMES[type] or raise ArgumentError, 'unknown ole type - 0x%04x' % type
289
+ (CLASS_MAP[type] || Data).load str
290
+ end
291
+
292
+ def self.dump type, variant
293
+ type = NAMES[type] or raise ArgumentError, 'unknown ole type - 0x%04x' % type
294
+ (CLASS_MAP[type] || Data).dump variant
295
+ end
296
+ end
297
+
298
+ include Variant::Constants
299
+
300
+ # deprecated aliases, kept mostly for the benefit of ruby-msg, until
301
+ # i release a new version.
302
+ def self.load_guid str
303
+ Variant.load VT_CLSID, str
304
+ end
305
+
306
+ def self.load_time str
307
+ Variant.load VT_FILETIME, str
308
+ end
309
+
310
+ FROM_UTF16 = Lpwstr::FROM_UTF16
311
+ TO_UTF16 = Lpwstr::TO_UTF16
312
+ end
313
+ end
314
+