ruby-ole 1.2.6 → 1.2.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.
- data/ChangeLog +12 -0
- data/README +27 -0
- data/Rakefile +2 -1
- data/bin/oletool +7 -1
- data/lib/ole/file_system.rb +2 -424
- data/lib/ole/storage.rb +3 -949
- data/lib/ole/storage/base.rb +948 -0
- data/lib/ole/storage/file_system.rb +444 -0
- data/lib/ole/storage/meta_data.rb +142 -0
- data/lib/ole/support.rb +23 -27
- data/lib/ole/types.rb +2 -243
- data/lib/ole/types/base.rb +247 -0
- data/lib/ole/types/property_set.rb +165 -0
- data/test/test_filesystem.rb +12 -8
- data/test/test_mbat.rb +2 -2
- data/test/test_meta_data.rb +45 -0
- data/test/test_property_set.rb +13 -13
- data/test/test_ranges_io.rb +10 -0
- data/test/test_storage.rb +91 -4
- data/test/test_types.rb +18 -5
- metadata +56 -42
- data/lib/ole/property_set.rb +0 -172
data/test/test_types.rb
CHANGED
@@ -3,36 +3,42 @@
|
|
3
3
|
$: << File.dirname(__FILE__) + '/../lib'
|
4
4
|
|
5
5
|
require 'test/unit'
|
6
|
-
require 'ole/types'
|
6
|
+
require 'ole/types/base'
|
7
7
|
|
8
8
|
class TestTypes < Test::Unit::TestCase
|
9
9
|
include Ole::Types
|
10
10
|
|
11
11
|
def test_lpwstr
|
12
|
-
# FIXME adhoc. the iconv-yness shouldn't be user's problem
|
13
12
|
assert_equal "t\000e\000s\000t\000", Lpwstr.dump('test')
|
14
13
|
str = Lpwstr.load "t\000e\000s\000t\000"
|
15
14
|
assert_equal 'test', str
|
16
15
|
assert_equal Lpwstr, str.class
|
17
16
|
end
|
18
17
|
|
18
|
+
def test_lpstr
|
19
|
+
# no null byte? probably wrong
|
20
|
+
assert_equal 'test', Lpstr.dump('test')
|
21
|
+
assert_equal 'test', Lpstr.load("test\000")
|
22
|
+
end
|
23
|
+
|
19
24
|
# in actual fact the same code path would be used for systime i expect
|
20
25
|
def test_filetime
|
21
26
|
# for saving, we can use Date, Time, or DateTime.
|
22
27
|
assert_equal "\000\000\260\3077-\307\001", FileTime.dump(Time.gm(2007, 1, 1))
|
23
28
|
time = FileTime.load "\000\000\260\3077-\307\001"
|
24
29
|
assert_equal FileTime, time.class
|
25
|
-
assert_equal
|
30
|
+
assert_equal '2007-01-01T00:00:00+00:00', time.to_s
|
26
31
|
# note that if we'd used Time.local, instead of gm, we'd get a different value. eg
|
27
32
|
assert_equal "\000\370\331\336\r-\307\001", FileTime.dump(DateTime.parse('2007-01-01 00:00 +0500'))
|
28
33
|
# note that it still loads up as GMT, because there's no associated time zone.
|
29
34
|
# essentially, i'm storing and loading times as GMT. maybe i should add in conversion to local time
|
30
35
|
# zone when loading
|
31
|
-
assert_equal
|
36
|
+
assert_equal '2006-12-31T19:00:00+00:00', FileTime.load("\000\370\331\336\r-\307\001").to_s
|
32
37
|
# test loading a bogus time
|
33
38
|
assert_equal nil, FileTime.load(0.chr * 8)
|
34
39
|
# this used to be counted as an "unlikely time", and discarded. that has been removed
|
35
|
-
assert_equal
|
40
|
+
assert_equal '1700-01-01T00:00:00+00:00', FileTime.load(FileTime.dump(Date.new(1700, 1, 1))).to_s
|
41
|
+
assert_equal '#<Ole::Types::FileTime 2006-12-31T19:00:00+00:00>', FileTime.load("\000\370\331\336\r-\307\001").inspect
|
36
42
|
end
|
37
43
|
|
38
44
|
def test_guid
|
@@ -50,5 +56,12 @@ class TestTypes < Test::Unit::TestCase
|
|
50
56
|
assert_equal Data, data.class
|
51
57
|
assert_equal 'blahblah', Variant.dump(VT_DATE, 'blahblah')
|
52
58
|
end
|
59
|
+
|
60
|
+
# purely for the purposes of coverage, i'll test these old aliases:
|
61
|
+
def test_deprecated_aliases
|
62
|
+
assert_equal '#<Ole::Types::Clsid:{00020329-0880-4007-c001-123456789046}>',
|
63
|
+
Ole::Types.load_guid("\x29\x03\x02\x00\x80\x08\x07\x40\xc0\x01\x12\x34\x56\x78\x90\x46").inspect
|
64
|
+
assert_equal '2006-12-31T19:00:00+00:00', Ole::Types.load_time("\000\370\331\336\r-\307\001").to_s
|
65
|
+
end
|
53
66
|
end
|
54
67
|
|
metadata
CHANGED
@@ -1,47 +1,46 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: ruby-ole
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.2.
|
7
|
-
|
8
|
-
summary: Ruby OLE library.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: aquasync@gmail.com
|
12
|
-
homepage: http://code.google.com/p/ruby-ole
|
13
|
-
rubyforge_project: ruby-ole
|
14
|
-
description: A library for easy read/write access to OLE compound documents for Ruby.
|
15
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
25
|
-
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
4
|
+
version: 1.2.7
|
5
|
+
platform: ""
|
29
6
|
authors:
|
30
7
|
- Charles Lowe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-08-12 00:00:00 +10:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A library for easy read/write access to OLE compound documents for Ruby.
|
17
|
+
email: aquasync@gmail.com
|
18
|
+
executables:
|
19
|
+
- oletool
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
31
24
|
files:
|
25
|
+
- README
|
32
26
|
- Rakefile
|
33
27
|
- ChangeLog
|
34
28
|
- data/propids.yaml
|
35
29
|
- bin/oletool
|
36
30
|
- lib/ole/base.rb
|
37
31
|
- lib/ole/file_system.rb
|
38
|
-
- lib/ole/property_set.rb
|
39
32
|
- lib/ole/ranges_io.rb
|
33
|
+
- lib/ole/storage/base.rb
|
34
|
+
- lib/ole/storage/file_system.rb
|
35
|
+
- lib/ole/storage/meta_data.rb
|
40
36
|
- lib/ole/storage.rb
|
41
37
|
- lib/ole/support.rb
|
38
|
+
- lib/ole/types/base.rb
|
39
|
+
- lib/ole/types/property_set.rb
|
42
40
|
- lib/ole/types.rb
|
43
41
|
- test/test_filesystem.rb
|
44
42
|
- test/test_mbat.rb
|
43
|
+
- test/test_meta_data.rb
|
45
44
|
- test/test_property_set.rb
|
46
45
|
- test/test_ranges_io.rb
|
47
46
|
- test/test_storage.rb
|
@@ -53,14 +52,9 @@ files:
|
|
53
52
|
- test/test_word_97.doc
|
54
53
|
- test/oleWithDirs.ole
|
55
54
|
- test/test_SummaryInformation
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
- test/test_property_set.rb
|
60
|
-
- test/test_ranges_io.rb
|
61
|
-
- test/test_storage.rb
|
62
|
-
- test/test_support.rb
|
63
|
-
- test/test_types.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://code.google.com/p/ruby-ole
|
57
|
+
post_install_message:
|
64
58
|
rdoc_options:
|
65
59
|
- --main
|
66
60
|
- README
|
@@ -68,13 +62,33 @@ rdoc_options:
|
|
68
62
|
- ruby-ole documentation
|
69
63
|
- --tab-width
|
70
64
|
- "2"
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
version:
|
77
79
|
requirements: []
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
+
rubyforge_project: ruby-ole
|
82
|
+
rubygems_version: 0.9.5
|
83
|
+
signing_key:
|
84
|
+
specification_version: 2
|
85
|
+
summary: Ruby OLE library.
|
86
|
+
test_files:
|
87
|
+
- test/test_filesystem.rb
|
88
|
+
- test/test_mbat.rb
|
89
|
+
- test/test_meta_data.rb
|
90
|
+
- test/test_property_set.rb
|
91
|
+
- test/test_ranges_io.rb
|
92
|
+
- test/test_storage.rb
|
93
|
+
- test/test_support.rb
|
94
|
+
- test/test_types.rb
|
data/lib/ole/property_set.rb
DELETED
@@ -1,172 +0,0 @@
|
|
1
|
-
require 'ole/types'
|
2
|
-
require 'yaml'
|
3
|
-
|
4
|
-
module Ole
|
5
|
-
module Types
|
6
|
-
#
|
7
|
-
# The PropertySet class currently supports readonly access to the properties
|
8
|
-
# serialized in "property set" streams, such as the file "\005SummaryInformation",
|
9
|
-
# in OLE files.
|
10
|
-
#
|
11
|
-
# Think has its roots in MFC property set serialization.
|
12
|
-
#
|
13
|
-
# See http://poi.apache.org/hpsf/internals.html for details
|
14
|
-
#
|
15
|
-
class PropertySet
|
16
|
-
HEADER_SIZE = 28
|
17
|
-
HEADER_PACK = "vvVa#{Clsid::SIZE}V"
|
18
|
-
OS_MAP = {
|
19
|
-
0 => :win16,
|
20
|
-
1 => :mac,
|
21
|
-
2 => :win32,
|
22
|
-
0x20001 => :ooffice, # open office on linux...
|
23
|
-
}
|
24
|
-
|
25
|
-
# define a smattering of the property set guids.
|
26
|
-
#FMTID_SummaryInformation = Clsid.parse '{f29f85e0-4ff9-1068-ab91-08002b27b3d9}'
|
27
|
-
#FMTID_DocSummaryInformation = Clsid.parse '{d5cdd502-2e9c-101b-9397-08002b2cf9ae}'
|
28
|
-
#FMTID_UserDefinedProperties = Clsid.parse '{d5cdd505-2e9c-101b-9397-08002b2cf9ae}'
|
29
|
-
|
30
|
-
DATA = YAML.load_file(File.dirname(__FILE__) + '/../../data/propids.yaml').
|
31
|
-
inject({}) { |hash, (key, value)| hash.update Clsid.parse(key) => value }
|
32
|
-
|
33
|
-
module Constants
|
34
|
-
DATA.each { |guid, (name, map)| const_set name, guid }
|
35
|
-
end
|
36
|
-
|
37
|
-
include Constants
|
38
|
-
|
39
|
-
class Section < Struct.new(:guid, :offset)
|
40
|
-
include Variant::Constants
|
41
|
-
include Enumerable
|
42
|
-
|
43
|
-
SIZE = Clsid::SIZE + 4
|
44
|
-
PACK = "a#{Clsid::SIZE}v"
|
45
|
-
|
46
|
-
attr_reader :length
|
47
|
-
def initialize str, property_set
|
48
|
-
@property_set = property_set
|
49
|
-
super(*str.unpack(PACK))
|
50
|
-
self.guid = Clsid.load guid
|
51
|
-
@map = DATA[guid] ? DATA[guid][1] : nil
|
52
|
-
load_header
|
53
|
-
end
|
54
|
-
|
55
|
-
def io
|
56
|
-
@property_set.io
|
57
|
-
end
|
58
|
-
|
59
|
-
def load_header
|
60
|
-
io.seek offset
|
61
|
-
@byte_size, @length = io.read(8).unpack 'V2'
|
62
|
-
end
|
63
|
-
|
64
|
-
def each
|
65
|
-
io.seek offset + 8
|
66
|
-
io.read(length * 8).scan(/.{8}/m).each do |str|
|
67
|
-
id, property_offset = str.unpack 'V2'
|
68
|
-
io.seek offset + property_offset
|
69
|
-
type, value = io.read(8).unpack('V2')
|
70
|
-
# is the method of serialization here custom?
|
71
|
-
case type
|
72
|
-
when VT_LPSTR, VT_LPWSTR
|
73
|
-
value = Variant.load type, io.read(value)
|
74
|
-
# ....
|
75
|
-
end
|
76
|
-
yield id, type, value
|
77
|
-
end
|
78
|
-
self
|
79
|
-
end
|
80
|
-
|
81
|
-
def [] key
|
82
|
-
unless Integer === key
|
83
|
-
return unless @map and key = @map.invert[key]
|
84
|
-
end
|
85
|
-
return unless result = properties.assoc(key)
|
86
|
-
result.last
|
87
|
-
end
|
88
|
-
|
89
|
-
def method_missing name, *args
|
90
|
-
if args.empty? and @map and @map.values.include? name.to_s
|
91
|
-
self[name.to_s]
|
92
|
-
else
|
93
|
-
super
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def properties
|
98
|
-
@properties ||= to_enum.to_a
|
99
|
-
end
|
100
|
-
|
101
|
-
#def to_h
|
102
|
-
# properties.inject({}) do |hash, (key, type, value)|
|
103
|
-
# hash.update
|
104
|
-
#end
|
105
|
-
end
|
106
|
-
|
107
|
-
attr_reader :io, :signature, :unknown, :os, :guid, :sections
|
108
|
-
def initialize io
|
109
|
-
@io = io
|
110
|
-
load_header io.read(HEADER_SIZE)
|
111
|
-
load_section_list io.read(@num_sections * Section::SIZE)
|
112
|
-
# expect no gap between last section and start of data.
|
113
|
-
#Log.warn "gap between section list and property data" unless io.pos == @sections.map(&:offset).min
|
114
|
-
end
|
115
|
-
|
116
|
-
def load_header str
|
117
|
-
@signature, @unknown, @os_id, @guid, @num_sections = str.unpack HEADER_PACK
|
118
|
-
# should i check that unknown == 0? it usually is. so is the guid actually
|
119
|
-
@guid = Clsid.load @guid
|
120
|
-
@os = OS_MAP[@os_id] || Log.warn("unknown operating system id #{@os_id}")
|
121
|
-
end
|
122
|
-
|
123
|
-
def load_section_list str
|
124
|
-
@sections = str.scan(/.{#{Section::SIZE}}/m).map { |s| Section.new s, self }
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
class Storage
|
130
|
-
# i'm thinking - search for a property set in +filenames+ containing a
|
131
|
-
# section with guid +guid+. then yield it. can read/write to it in the
|
132
|
-
# block.
|
133
|
-
# propsets themselves can have guids, but they are often all null.
|
134
|
-
def with_property_set guid, filenames=nil
|
135
|
-
end
|
136
|
-
|
137
|
-
class PropertySetSectionProxy
|
138
|
-
attr_reader :obj, :section_num
|
139
|
-
def initialize obj, section_num
|
140
|
-
@obj, @section_num = obj, section_num
|
141
|
-
end
|
142
|
-
|
143
|
-
def method_missing name, *args, &block
|
144
|
-
obj.open do |io|
|
145
|
-
section = Types::PropertySet.new(io).sections[section_num]
|
146
|
-
section.send name, *args, &block
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# this will be changed to use with_property_set
|
152
|
-
def summary_information
|
153
|
-
dirent = root["\005SummaryInformation"]
|
154
|
-
dirent.open do |io|
|
155
|
-
propset = Types::PropertySet.new(io)
|
156
|
-
sections = propset.sections
|
157
|
-
# this will maybe get wrapped up as
|
158
|
-
# section = propset[guid]
|
159
|
-
# maybe taking it one step further, i'd hide the section thing,
|
160
|
-
# and let you use composite keys, like
|
161
|
-
# propset[4, guid] eg in MAPI, and just propset.doc_author.
|
162
|
-
section = sections.find do |s|
|
163
|
-
s.guid == Types::PropertySet::FMTID_SummaryInformation
|
164
|
-
end
|
165
|
-
return PropertySetSectionProxy.new(dirent, sections.index(section))
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
alias summary_info :summary_information
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|