ffi-chm 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -9,12 +9,15 @@ Ruby bindings for the libchm via FFI.
9
9
  == FEATURES/PROBLEMS:
10
10
 
11
11
  * Provides basic access to the CHM files.
12
- * Currently developed on MRI 1.8 / 1.9
12
+ * Some functionalities to access to internal structure.
13
+ * Currently developed on MRI 1.8.7 / 1.9.2
13
14
 
14
15
  == SYNOPSIS:
15
16
 
16
17
  FFI::Chm::ChmFile.new("reference-manual.chm") do |chm|
17
18
  chm.enumerate(:normal, :files).map{|ui| ui[:path]}
19
+
20
+ puts chm.title
18
21
  end
19
22
 
20
23
  == REQUIREMENTS:
@@ -0,0 +1,39 @@
1
+ require 'nokogiri'
2
+ require 'ffi-chm/com8ble'
3
+
4
+ module FFI::Chm::ChmFile::Aux
5
+ include FFI::Chm
6
+
7
+ def system
8
+ @system ||= Struct::System.new.read self.retrieve_object("/#SYSTEM")
9
+ end
10
+
11
+ def encoding
12
+ @encoding ||= self.system.encoding
13
+ end
14
+
15
+ def title
16
+ @title ||= self.system.record(3).data.title.com8ble.force_encoding(encoding).encode("UTF-8")
17
+ end
18
+
19
+ def contents(cache=true)
20
+ return @contents if @contents
21
+ xml = self.retrieve_object("/#{self.system.record(6).data.compiled_file}.hhc").com8ble.force_encoding(encoding).encode("UTF-8")
22
+ doc = Struct::HHXDocument.new(Struct::HHC::Entry)
23
+ Nokogiri::HTML::SAX::Parser.new(doc).parse(xml)
24
+
25
+ doc.root.first.tap do |v|
26
+ @contents = v if cache
27
+ end
28
+ end
29
+
30
+ def index(cache=true)
31
+ return @index if @index
32
+ xml = self.retrieve_object("/#{self.system.record(6).data.compiled_file}.hhk").com8ble.force_encoding(encoding).encode("UTF-8")
33
+ doc = Struct::HHXDocument.new(Struct::HHK::Entry)
34
+ Nokogiri::HTML::SAX::Parser.new(doc).parse(xml)
35
+ doc.root.flatten.tap do |v|
36
+ @index = v if cache
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,102 @@
1
+ module FFI::Chm
2
+ class ChmFile
3
+ require 'ffi-chm/chm_file/aux'
4
+ include Aux
5
+
6
+ attr_reader :path
7
+
8
+ def initialize(path, &block)
9
+ @path = path
10
+ self.open &block if block_given?
11
+ end
12
+
13
+ def open(path=@path, &block)
14
+ @path = path
15
+ @h = API.chm_open @path
16
+ raise ChmError, "Not exists?" if @h.null?
17
+ if block_given?
18
+ begin
19
+ yield self
20
+ ensure
21
+ self.close
22
+ end
23
+ else
24
+ self
25
+ end
26
+ end
27
+
28
+ def close
29
+ API.chm_close @h unless @h.null?
30
+ end
31
+
32
+ def set_param(param, value)
33
+ API.chm_set_param @h, param, value
34
+ end
35
+
36
+ def resolve_object(name)
37
+ ui = UnitInfo.new
38
+ case API.chm_resolve_object @h, name, ui
39
+ when Const::RESOLVE_SUCCESS
40
+ ui
41
+ when Const::RESOLVE_FAILURE
42
+ raise ResolveError
43
+ end
44
+ end
45
+
46
+ def retrieve_object(ui)
47
+ if UnitInfo === ui
48
+ buf = FFI::Buffer.new ui[:length]
49
+ API.chm_retrieve_object @h, ui, buf, 0, ui[:length]
50
+ buf.read_bytes buf.size
51
+ else
52
+ retrieve_object resolve_object(ui)
53
+ end
54
+ rescue ResolveError
55
+ raise RetrieveError, ui
56
+ end
57
+
58
+ require 'enumerator'
59
+ def enumerate(*what, &block)
60
+ if block_given?
61
+ API.chm_enumerate @h, to_flags(what), enum_func(&block), nil
62
+ self
63
+ else
64
+ (Enumerable::Enumerator rescue Enumerator).new self, :enumerate, to_flags(what)
65
+ end
66
+ end
67
+
68
+ def enumerate_dir(prefix, *what, &block)
69
+ if block_given?
70
+ API.chm_enumerate_dir @h, prefix, to_flags(what), enum_func(&block), nil
71
+ self
72
+ else
73
+ (Enumerable::Enumerator rescue Enumerator).new self, :enumerate_dir, prefix, to_flags(what)
74
+ end
75
+ end
76
+
77
+ private
78
+ def enum_func(&block)
79
+ lambda do |h, ui, _|
80
+ begin
81
+ if block.call(UnitInfo.new(ui).clone) == :break
82
+ Const::ENUMERATOR_SUCCESS
83
+ else
84
+ Const::ENUMERATOR_CONTINUE
85
+ end
86
+ rescue
87
+ Const::ENUMERATOR_FAILURE
88
+ end
89
+ end
90
+ end
91
+
92
+ def to_flags(what)
93
+ if what.size == 1 && Integer === what.first
94
+ what.first
95
+ else
96
+ what.inject(0) do |r, sym|
97
+ r | (Const::ENUMERATE_FLAGS[sym] || 0)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,37 @@
1
+ module Com8ble
2
+ module String
3
+ require 'iconv'
4
+
5
+ def force_encoding(encoding)
6
+ @encoding = _encoding(encoding)
7
+ self
8
+ end
9
+
10
+ def encoding
11
+ @encoding
12
+ end
13
+
14
+ def encode(encoding)
15
+ r = Iconv.conv(_encoding(encoding).name, @encoding.name, self)
16
+ r.extend String
17
+ r.force_encoding encoding
18
+ r
19
+ end
20
+
21
+ private
22
+ def _encoding(encoding)
23
+ if ::String === encoding
24
+ Struct.new(:name).new(encoding)
25
+ else
26
+ encoding
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ class String
33
+ def com8ble
34
+ self.extend Com8ble::String unless self.respond_to? :force_encoding
35
+ self
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ module FFI::Chm::Struct::HHC
2
+ class Entry < Struct.new(:Name, :Type, :Local, :URL, :FrameName, :WindowName, :Comment, :Merge, :ImageNumber, :New)
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module FFI::Chm::Struct::HHK
2
+ class Entry < Struct.new(:Name, :Type, :Local, :URL, :FrameName, :WindowName, :Comment, :Merge, :'See Also')
3
+ def SeeAlso
4
+ self["See Also"]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ module FFI::Chm::Struct
2
+ class HHXDocument < Nokogiri::XML::SAX::Document
3
+ # ul - li - object - param
4
+ attr_reader :root
5
+
6
+ def initialize(entry_klass)
7
+ @klass = entry_klass
8
+ end
9
+
10
+ def start_document
11
+ @root = []
12
+ @heads = []
13
+ @heads << @root
14
+ end
15
+
16
+ def start_element(name, attributes = [])
17
+ case name
18
+ when "param"
19
+ return unless @entry
20
+ attr = {}
21
+ attributes.each_slice(2){|k, v| attr[k.intern] = v}
22
+ @entry[attr[:name].intern] = attr[:value]
23
+ when "li"
24
+ @entry = @klass.new
25
+ when "ul"
26
+ head = []
27
+ @heads.last << head
28
+ @heads << head
29
+ end
30
+ end
31
+
32
+ def end_element(name, *attributes)
33
+ case name
34
+ when "object" # li results broken structure.
35
+ @heads.last << @entry if @entry
36
+ when "ul"
37
+ @heads.pop
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ class FFI::Chm::Struct::System::Code3 < BinData::Record
2
+ stringz :title
3
+ end
@@ -0,0 +1,11 @@
1
+ class FFI::Chm::Struct::System::Code4 < BinData::Record
2
+ endian :little
3
+
4
+ uint32 :lcid
5
+ uint32 :use_dbcs
6
+ uint32 :fulltext_enabled
7
+ uint32 :has_klinks
8
+ uint32 :has_alinks
9
+ uint64 :timestamp
10
+ uint64 :unknown
11
+ end
@@ -0,0 +1,3 @@
1
+ class FFI::Chm::Struct::System::Code6 < BinData::Record
2
+ stringz :compiled_file
3
+ end
@@ -0,0 +1,17 @@
1
+ class FFI::Chm::Struct::System::Record < BinData::Record
2
+ require 'ffi-chm/struct/system/code3'
3
+ require 'ffi-chm/struct/system/code4'
4
+ require 'ffi-chm/struct/system/code6'
5
+
6
+ endian :little
7
+
8
+ uint16 :code
9
+ uint16 :len
10
+
11
+ choice :data, :selection => :code do
12
+ code3 3
13
+ code4 4
14
+ code6 6
15
+ string :default, :read_length => :len
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ class FFI::Chm::Struct::System < BinData::Record
2
+ require 'ffi-chm/struct/system/record'
3
+
4
+ endian :little
5
+
6
+ uint32 :version
7
+ array :records, :type => :record, :read_until => :eof
8
+
9
+
10
+ def record(code)
11
+ @memo ||= Hash[*records.map{|v|[v.code.to_i, v]}.flatten]
12
+ @memo[code]
13
+ end
14
+
15
+ def encoding
16
+ case self.record(4).data.lcid.to_i
17
+ when 1041
18
+ "CP932"
19
+ else
20
+ "UTF-8"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ require 'bindata'
2
+
3
+ module FFI::Chm::Struct
4
+ autoload :System, 'ffi-chm/struct/system'
5
+ autoload :HHK, 'ffi-chm/struct/hhk/entry'
6
+ autoload :HHC, 'ffi-chm/struct/hhc/entry'
7
+ autoload :HHXDocument, 'ffi-chm/struct/hhx_document'
8
+ end
@@ -0,0 +1,28 @@
1
+ module FFI::Chm
2
+ class UnitInfo < FFI::Struct
3
+ layout :start, :uint64,
4
+ :length, :uint64,
5
+ :space, :int,
6
+ :flags, :int,
7
+ :path, [:char, Const::MAX_PATHLEN + 1]
8
+
9
+ def dir?
10
+ flag? :dirs
11
+ end
12
+
13
+ def file?
14
+ flag? :files
15
+ end
16
+
17
+ %w(normal special meta).each do |name|
18
+ define_method("#{name}?") do
19
+ flag? name.intern
20
+ end
21
+ end
22
+
23
+ private
24
+ def flag?(flag)
25
+ !!(self[:flags] & Const::ENUMERATE_FLAGS[flag]).nonzero?
26
+ end
27
+ end
28
+ end
data/lib/ffi-chm.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module FFI
2
2
  module Chm
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
6
6
 
@@ -8,125 +8,14 @@ require 'ffi-chm/const'
8
8
  require 'ffi-chm/api'
9
9
 
10
10
  module FFI::Chm
11
+ autoload :Struct, 'ffi-chm/struct'
12
+ autoload :ChmFile, 'ffi-chm/chm_file'
13
+ autoload :UnitInfo, 'ffi-chm/unit_info'
14
+
11
15
  include API
12
16
  include Const
13
17
 
14
18
  class ChmError < StandardError;end
15
19
  class ResolveError < ChmError;end
16
20
  class RetrieveError < ChmError;end
17
-
18
- class ChmFile
19
- def initialize(fn)
20
- @h = API.chm_open fn
21
- raise ChmError, "Not exists?" if @h.null?
22
- if block_given?
23
- begin
24
- yield self
25
- ensure
26
- self.close
27
- end
28
- end
29
- end
30
-
31
- def close
32
- API.chm_close @h unless @h.null?
33
- end
34
-
35
- def set_param(param, value)
36
- API.chm_set_param @h, param, value
37
- end
38
-
39
- def resolve_object(name)
40
- ui = UnitInfo.new
41
- case API.chm_resolve_object @h, name, ui
42
- when Const::RESOLVE_SUCCESS
43
- ui
44
- when Const::RESOLVE_FAILURE
45
- raise ResolveError
46
- end
47
- end
48
-
49
- def retrieve_object(ui)
50
- if UnitInfo === ui
51
- buf = FFI::Buffer.new ui[:length]
52
- API.chm_retrieve_object @h, ui, buf, 0, ui[:length]
53
- buf.read_bytes buf.size
54
- else
55
- retrieve_object resolve_object(ui)
56
- end
57
- rescue ResolveError
58
- raise RetrieveError, ui
59
- end
60
-
61
- require 'enumerator'
62
- def enumerate(*what, &block)
63
- if block_given?
64
- API.chm_enumerate @h, to_flags(what), enum_func(&block), nil
65
- self
66
- else
67
- (Enumerable::Enumerator rescue Enumerator).new self, :enumerate, to_flags(what)
68
- end
69
- end
70
-
71
- def enumerate_dir(prefix, *what, &block)
72
- if block_given?
73
- API.chm_enumerate_dir @h, prefix, to_flags(what), enum_func(&block), nil
74
- self
75
- else
76
- (Enumerable::Enumerator rescue Enumerator).new self, :enumerate_dir, prefix, to_flags(what)
77
- end
78
- end
79
-
80
- private
81
- def enum_func(&block)
82
- lambda do |h, ui, _|
83
- begin
84
- if block.call(UnitInfo.new(ui).clone) == :break
85
- Const::ENUMERATOR_SUCCESS
86
- else
87
- Const::ENUMERATOR_CONTINUE
88
- end
89
- rescue
90
- Const::ENUMERATOR_FAILURE
91
- end
92
- end
93
- end
94
-
95
- def to_flags(what)
96
- if what.size == 1 && Integer === what.first
97
- what.first
98
- else
99
- what.inject(0) do |r, sym|
100
- r | (Const::ENUMERATE_FLAGS[sym] || 0)
101
- end
102
- end
103
- end
104
- end
105
-
106
- class UnitInfo < FFI::Struct
107
- layout :start, :uint64,
108
- :length, :uint64,
109
- :space, :int,
110
- :flags, :int,
111
- :path, [:char, Const::MAX_PATHLEN + 1]
112
-
113
- def dir?
114
- flag? :dirs
115
- end
116
-
117
- def file?
118
- flag? :files
119
- end
120
-
121
- %w(normal special meta).each do |name|
122
- define_method("#{name}?") do
123
- flag? name.intern
124
- end
125
- end
126
-
127
- private
128
- def flag?(flag)
129
- !!(self[:flags] & Const::ENUMERATE_FLAGS[flag]).nonzero?
130
- end
131
- end
132
21
  end
metadata CHANGED
@@ -1,102 +1,112 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ffi-chm
3
- version: !ruby/object:Gem::Version
4
- hash: 27
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 1
9
- - 0
10
- version: 0.1.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - nanki
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-07-28 00:00:00 +09:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-04 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: ffi
16
+ requirement: &70275895477860 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
23
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: *70275895477860
25
+ - !ruby/object:Gem::Dependency
26
+ name: bindata
27
+ requirement: &70275895475840 !ruby/object:Gem::Requirement
25
28
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70275895475840
36
+ - !ruby/object:Gem::Dependency
37
+ name: nokogiri
38
+ requirement: &70275895474760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
33
44
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: hoe
37
45
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
46
+ version_requirements: *70275895474760
47
+ - !ruby/object:Gem::Dependency
48
+ name: hoe
49
+ requirement: &70275895473500 !ruby/object:Gem::Requirement
39
50
  none: false
40
- requirements:
51
+ requirements:
41
52
  - - ~>
42
- - !ruby/object:Gem::Version
43
- hash: 23
44
- segments:
45
- - 2
46
- - 10
47
- version: "2.10"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.10'
48
55
  type: :development
49
- version_requirements: *id002
56
+ prerelease: false
57
+ version_requirements: *70275895473500
50
58
  description: Ruby bindings for the libchm via FFI.
51
- email:
59
+ email:
52
60
  - nanki@dotswitch.net
53
61
  executables: []
54
-
55
62
  extensions: []
56
-
57
- extra_rdoc_files:
63
+ extra_rdoc_files:
58
64
  - README.txt
59
- files:
65
+ files:
60
66
  - README.txt
61
- - lib/ffi-chm.rb
62
67
  - lib/ffi-chm/api.rb
68
+ - lib/ffi-chm/chm_file/aux.rb
69
+ - lib/ffi-chm/chm_file.rb
70
+ - lib/ffi-chm/com8ble.rb
63
71
  - lib/ffi-chm/const.rb
72
+ - lib/ffi-chm/struct/hhc/entry.rb
73
+ - lib/ffi-chm/struct/hhk/entry.rb
74
+ - lib/ffi-chm/struct/hhx_document.rb
75
+ - lib/ffi-chm/struct/system/code3.rb
76
+ - lib/ffi-chm/struct/system/code4.rb
77
+ - lib/ffi-chm/struct/system/code6.rb
78
+ - lib/ffi-chm/struct/system/record.rb
79
+ - lib/ffi-chm/struct/system.rb
80
+ - lib/ffi-chm/struct.rb
81
+ - lib/ffi-chm/unit_info.rb
82
+ - lib/ffi-chm.rb
64
83
  - test/test_ffi_chm.rb
65
84
  - .gemtest
66
- has_rdoc: true
67
85
  homepage: http://github.com/nanki/ffi-chm
68
86
  licenses: []
69
-
70
87
  post_install_message:
71
- rdoc_options:
88
+ rdoc_options:
72
89
  - --main
73
90
  - README.txt
74
- require_paths:
91
+ require_paths:
75
92
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
93
+ required_ruby_version: !ruby/object:Gem::Requirement
77
94
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
100
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
94
105
  requirements: []
95
-
96
106
  rubyforge_project: ffi-chm
97
- rubygems_version: 1.6.2
107
+ rubygems_version: 1.8.6
98
108
  signing_key:
99
109
  specification_version: 3
100
110
  summary: Ruby bindings for the libchm via FFI.
101
- test_files:
111
+ test_files:
102
112
  - test/test_ffi_chm.rb