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.
- checksums.yaml +7 -0
- data/COPYING +20 -0
- data/ChangeLog +140 -0
- data/README +116 -0
- data/Rakefile +91 -0
- data/bin/oletool +40 -0
- data/lib/ole/base.rb +10 -0
- data/lib/ole/file_system.rb +7 -0
- data/lib/ole/ranges_io.rb +267 -0
- data/lib/ole/storage.rb +3 -0
- data/lib/ole/storage/base.rb +945 -0
- data/lib/ole/storage/file_system.rb +394 -0
- data/lib/ole/storage/meta_data.rb +150 -0
- data/lib/ole/storage/version.rb +8 -0
- data/lib/ole/support.rb +248 -0
- data/lib/ole/types.rb +2 -0
- data/lib/ole/types/base.rb +314 -0
- data/lib/ole/types/property_set.rb +202 -0
- data/ruby-ole.gemspec +32 -0
- data/test/oleWithDirs.ole +0 -0
- data/test/test.doc +0 -0
- data/test/test_SummaryInformation +0 -0
- data/test/test_filesystem.rb +932 -0
- data/test/test_mbat.rb +40 -0
- data/test/test_meta_data.rb +43 -0
- data/test/test_property_set.rb +40 -0
- data/test/test_ranges_io.rb +113 -0
- data/test/test_storage.rb +221 -0
- data/test/test_support.rb +155 -0
- data/test/test_types.rb +68 -0
- data/test/test_word_6.doc +0 -0
- data/test/test_word_95.doc +0 -0
- data/test/test_word_97.doc +0 -0
- metadata +92 -0
data/lib/ole/support.rb
ADDED
@@ -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,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
|
+
|