origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,24 +1,20 @@
1
1
  =begin
2
- = File
3
- dictionary.rb
4
-
5
- = Info
6
- This file is part of Origami, PDF manipulation framework for Ruby
7
- Copyright (C) 2010 Guillaume DelugrÈ <guillaume AT security-labs DOT org>
8
- All right reserved.
9
-
10
- Origami is free software: you can redistribute it and/or modify
11
- it under the terms of the GNU Lesser General Public License as published by
12
- the Free Software Foundation, either version 3 of the License, or
13
- (at your option) any later version.
14
-
15
- Origami is distributed in the hope that it will be useful,
16
- but WITHOUT ANY WARRANTY; without even the implied warranty of
17
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
- GNU Lesser General Public License for more details.
19
-
20
- You should have received a copy of the GNU Lesser General Public License
21
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
2
+
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
22
18
 
23
19
  =end
24
20
 
@@ -26,247 +22,272 @@ module Origami
26
22
 
27
23
  class InvalidDictionaryObjectError < InvalidObjectError #:nodoc:
28
24
  end
29
-
25
+
30
26
  #
31
27
  # Class representing a Dictionary Object.
32
28
  # Dictionaries are containers associating a Name to an embedded Object.
33
29
  #
34
30
  class Dictionary < Hash
35
- include Origami::Object
36
-
37
- TOKENS = %w{ << >> } #:nodoc:
38
- @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
39
- @@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
40
-
41
- @@cast_fingerprints = {}
42
- attr_reader :strings_cache, :names_cache, :xref_cache
43
-
44
- #
45
- # Creates a new Dictionary.
46
- # _hash_:: The hash representing the new Dictionary.
47
- #
48
- def initialize(hash = {})
49
- raise TypeError, "Expected type Hash, received #{hash.class}." unless hash.is_a?(Hash)
50
- super()
51
-
52
- @strings_cache = []
53
- @names_cache = []
54
- @xref_cache = {}
55
-
56
- hash.each_pair do |k,v|
57
- @names_cache.push(k.to_o)
58
- case val = v.to_o
59
- when String then @strings_cache.push(val)
60
- when Name then @names_cache.push(val)
61
- when Reference then
62
- (@xref_cache[val] ||= []).push(self)
63
- when Dictionary,Array then
64
- @strings_cache.concat(val.strings_cache)
65
- @names_cache.concat(val.names_cache)
66
- @xref_cache.update(val.xref_cache) do |ref, cache1, cache2|
67
- cache1.concat(cache2)
68
- end
69
-
70
- val.strings_cache.clear
71
- val.names_cache.clear
72
- val.xref_cache.clear
73
- end
74
-
75
- self[k.to_o] = val unless k.nil?
76
- end
77
- end
78
-
79
- def self.parse(stream, parser = nil) #:nodoc:
80
-
81
- offset = stream.pos
82
-
83
- if stream.skip(@@regexp_open).nil?
84
- raise InvalidDictionaryObjectError, "No token '#{TOKENS.first}' found"
85
- end
86
-
87
- pairs = {}
88
- while stream.skip(@@regexp_close).nil? do
89
- key = Name.parse(stream, parser)
90
-
91
- type = Object.typeof(stream)
92
- if type.nil?
93
- raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}"
94
- end
95
-
96
- value = type.parse(stream, parser)
97
- pairs[key] = value
98
- end
99
-
100
- dict =
101
- if Origami::OPTIONS[:enable_type_guessing]
102
- guessed_type = self.guess_type(pairs)
103
-
104
- if Origami::OPTIONS[:enable_type_propagation]
105
- guessed_type.new(
106
- Hash[
107
- pairs.map {|key, value|
108
- hint_type = guessed_type.hint_type(key.value)
109
- if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
110
- hint_type.find {|type| type.native_type == value.native_type}
31
+ include Origami::Object
32
+ using TypeConversion
33
+
34
+ TOKENS = %w{ << >> } #:nodoc:
35
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first + WHITESPACES)
36
+ @@regexp_close = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
37
+
38
+ @@cast_fingerprints = {}
39
+ @@cast_keys = []
40
+
41
+ attr_reader :strings_cache, :names_cache, :xref_cache
42
+
43
+ #
44
+ # Creates a new Dictionary.
45
+ # _hash_:: The hash representing the new Dictionary.
46
+ #
47
+ def initialize(hash = {}, parser = nil)
48
+ raise TypeError, "Expected type Hash, received #{hash.class}." unless hash.is_a?(Hash)
49
+ super()
50
+
51
+ @strings_cache = []
52
+ @names_cache = []
53
+ @xref_cache = {}
54
+
55
+ hash.each_pair do |k,v|
56
+ next if k.nil?
57
+
58
+ # Turns the values into Objects.
59
+ key, value = k.to_o, v.to_o
60
+
61
+ if Origami::OPTIONS[:enable_type_guessing]
62
+ hint_type = guess_value_type(key, value)
63
+
64
+ if hint_type.is_a?(Class) and hint_type < value.class
65
+ value = value.cast_to(hint_type, parser)
111
66
  end
112
67
 
113
- if hint_type.is_a?(Class) and hint_type.native_type == value.native_type
114
- [key, value.cast_to(hint_type)]
115
- elsif hint_type and value.is_a?(Reference) and parser
116
- parser.defer_type_cast(value, hint_type)
117
- [key, value]
118
- else
119
- [key, value]
68
+ if hint_type and parser and Origami::OPTIONS[:enable_type_propagation]
69
+ if value.is_a?(Reference)
70
+ parser.defer_type_cast(value, hint_type)
71
+ end
120
72
  end
121
- }])
73
+ end
74
+
75
+ # Cache keys and values for fast search.
76
+ cache_key(key)
77
+ cache_value(value)
78
+
79
+ self[key] = value
80
+ end
81
+ end
82
+
83
+ def self.parse(stream, parser = nil) #:nodoc:
84
+ offset = stream.pos
85
+
86
+ if stream.skip(@@regexp_open).nil?
87
+ raise InvalidDictionaryObjectError, "No token '#{TOKENS.first}' found"
88
+ end
89
+
90
+ hash = {}
91
+ while stream.skip(@@regexp_close).nil? do
92
+ key = Name.parse(stream, parser)
93
+
94
+ type = Object.typeof(stream)
95
+ raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}" if type.nil?
96
+
97
+ value = type.parse(stream, parser)
98
+ hash[key] = value
99
+ end
100
+
101
+ if Origami::OPTIONS[:enable_type_guessing] and not (@@cast_keys & hash.keys).empty?
102
+ dict_type = self.guess_type(hash)
122
103
  else
123
- guessed_type.new(pairs)
104
+ dict_type = self
124
105
  end
125
- else
126
- self.new(pairs)
127
- end
128
-
129
- dict.file_offset = offset
130
-
131
- dict
132
- end
133
-
134
- alias to_h to_hash
135
-
136
- def to_s(indent = 1) #:nodoc:
137
- if indent > 0
138
- content = TOKENS.first + EOL
139
- self.each_pair do |key,value|
140
- content << "\t" * indent + key.to_s + " " + (value.is_a?(Dictionary) ? value.to_s(indent + 1) : value.to_s) + EOL
141
- end
142
-
143
- content << "\t" * (indent - 1) + TOKENS.last
144
- else
145
- content = TOKENS.first.dup
146
- self.each_pair do |key,value|
147
- content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(0) : value.to_s}"
148
- end
149
- content << TOKENS.last
106
+
107
+ # Creates the Dictionary.
108
+ dict = dict_type.new(hash, parser)
109
+
110
+ dict.file_offset = offset
111
+ dict
150
112
  end
151
113
 
152
- super(content)
153
- end
154
-
155
- def map!(&b)
156
- self.each_pair do |k,v|
157
- self[k] = b.call(v)
114
+ def to_s(indent: 1, tab: "\t") #:nodoc:
115
+ if indent > 0
116
+ content = TOKENS.first + EOL
117
+ self.each_pair do |key,value|
118
+ content << tab * indent << key.to_s << ' '
119
+ content << (value.is_a?(Dictionary) ? value.to_s(indent: indent+1) : value.to_s)
120
+ content << EOL
121
+ end
122
+
123
+ content << tab * (indent - 1) << TOKENS.last
124
+ else
125
+ content = TOKENS.first.dup
126
+ self.each_pair do |key,value|
127
+ content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(indent: 0) : value.to_s}"
128
+ end
129
+ content << TOKENS.last
130
+ end
131
+
132
+ super(content)
158
133
  end
159
- end
160
-
161
- def merge(dict)
162
- Dictionary.new(super(dict))
163
- end
164
-
165
- def []=(key,val)
166
- unless key.is_a?(Symbol) or key.is_a?(Name)
167
- fail "Expecting a Name for a Dictionary entry, found #{key.class} instead."
134
+
135
+ def map!(&b)
136
+ self.each_pair do |k,v|
137
+ self[k] = b.call(v)
138
+ end
168
139
  end
169
-
170
- key = key.to_o
171
- if not val.nil?
172
- val = val.to_o
173
- super(key,val)
174
-
175
- key.parent = self
176
- val.parent = self unless val.is_indirect? or val.parent.equal?(self)
177
-
178
- val
179
- else
180
- delete(key)
140
+
141
+ def merge(dict)
142
+ Dictionary.new(super(dict))
181
143
  end
182
- end
183
-
184
- def [](key)
185
- super(key.to_o)
186
- end
187
-
188
- def has_key?(key)
189
- super(key.to_o)
190
- end
191
-
192
- def delete(key)
193
- super(key.to_o)
194
- end
195
-
196
- def cast_to(type)
197
- super(type)
198
-
199
- cast = type.new(self)
200
- cast.parent = self.parent
201
- cast.no, cast.generation = self.no, self.generation
202
- if self.is_indirect?
203
- cast.set_indirect(true)
204
- cast.set_pdf(self.pdf)
205
- cast.file_offset = self.file_offset # cast can replace self
144
+
145
+ def []=(key,val)
146
+ unless key.is_a?(Symbol) or key.is_a?(Name)
147
+ fail "Expecting a Name for a Dictionary entry, found #{key.class} instead."
148
+ end
149
+
150
+ key = key.to_o
151
+ if val.nil?
152
+ delete(key)
153
+ return
154
+ end
155
+
156
+ val = val.to_o
157
+ super(key,val)
158
+
159
+ key.parent = self
160
+ val.parent = self unless val.indirect? or val.parent.equal?(self)
161
+
162
+ val
206
163
  end
207
164
 
208
- cast.xref_cache.update(self.xref_cache)
209
- cast.names_cache.concat(self.names_cache)
210
- cast.strings_cache.concat(self.strings_cache)
165
+ def [](key)
166
+ super(key.to_o)
167
+ end
211
168
 
212
- cast
213
- end
169
+ def key?(key)
170
+ super(key.to_o)
171
+ end
172
+ alias include? key?
173
+ alias has_key? key?
214
174
 
215
- alias each each_value
175
+ def delete(key)
176
+ super(key.to_o)
177
+ end
216
178
 
217
- alias value to_h
179
+ def cast_to(type, parser = nil)
180
+ super(type)
218
181
 
219
- def method_missing(field, *args) #:nodoc:
220
- raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/
182
+ cast = type.new(self, parser)
183
+ cast.parent = self.parent
184
+ cast.no, cast.generation = self.no, self.generation
185
+ if self.indirect?
186
+ cast.set_indirect(true)
187
+ cast.set_document(self.document)
188
+ cast.file_offset = self.file_offset # cast can replace self
189
+ end
221
190
 
222
- if field.to_s[-1,1] == '='
223
- self[field.to_s[0..-2].to_sym] = args.first
224
- else
225
- obj = self[field];
226
- obj.is_a?(Reference) ? obj.solve : obj
191
+ cast
227
192
  end
228
- end
229
-
230
- def copy
231
- copy = self.class.new
232
- self.each_pair do |k,v|
233
- copy[k] = v.copy
234
- end
235
-
236
- copy.parent = @parent
237
- copy.no, copy.generation = @no, @generation
238
- copy.set_indirect(true) if is_indirect?
239
- copy.set_pdf(@pdf) if is_indirect?
240
- copy
241
- end
242
-
243
- def self.native_type; Dictionary end
244
-
245
- def self.add_type_info(typeclass, key, value) #:nodoc:
246
- if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Dictionary and
247
- @@cast_fingerprints.has_key?(typeclass.superclass)
248
- @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
193
+
194
+ alias each each_value
195
+
196
+ def to_h
197
+ Hash[self.to_a.map!{|k, v| [ k.value, v.value ]}]
249
198
  end
199
+ alias value to_h
250
200
 
251
- @@cast_fingerprints[typeclass] ||= {}
252
- @@cast_fingerprints[typeclass][key.to_o] = value.to_o
253
- end
201
+ def method_missing(field, *args) #:nodoc:
202
+ raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/
254
203
 
255
- def self.guess_type(hash) #:nodoc:
256
- best_type = self
204
+ if field.to_s[-1,1] == '='
205
+ self[field.to_s[0..-2].to_sym] = args.first
206
+ else
207
+ obj = self[field];
208
+ obj.is_a?(Reference) ? obj.solve : obj
209
+ end
210
+ end
211
+
212
+ def copy
213
+ copy = self.class.new
214
+ self.each_pair do |k,v|
215
+ copy[k] = v.copy
216
+ end
217
+
218
+ copy.parent = @parent
219
+ copy.no, copy.generation = @no, @generation
220
+ copy.set_indirect(true) if self.indirect?
221
+ copy.set_document(@document) if self.indirect?
257
222
 
258
- @@cast_fingerprints.each_pair do |typeclass, keys|
259
- best_type = typeclass if keys.all? { |k,v|
260
- hash.has_key?(k) and hash[k] == v
261
- } and typeclass < best_type
223
+ copy
262
224
  end
263
225
 
264
- best_type
265
- end
226
+ def self.native_type; Dictionary end
227
+
228
+ def self.add_type_info(klass, key, value) #:nodoc:
229
+ raise TypeError, "Invalid class #{klass}" unless klass.is_a?(Class) and klass < Dictionary
230
+
231
+ key, value = key.to_o, value.to_o
232
+
233
+ # Inherit the superclass type information.
234
+ if not @@cast_fingerprints.key?(klass) and @@cast_fingerprints.key?(klass.superclass)
235
+ @@cast_fingerprints[klass] = @@cast_fingerprints[klass.superclass].dup
236
+ end
237
+
238
+ @@cast_fingerprints[klass] ||= {}
239
+ @@cast_fingerprints[klass][key] = value
266
240
 
267
- def self.hint_type(name); nil end #:nodoc:
241
+ @@cast_keys.push(key) unless @@cast_keys.include?(key)
242
+ end
243
+
244
+ def self.guess_type(hash) #:nodoc:
245
+ best_type = self
246
+
247
+ @@cast_fingerprints.each_pair do |klass, keys|
248
+ next unless klass < best_type
249
+
250
+ best_type = klass if keys.all? { |k,v| hash[k] == v }
251
+ end
252
+
253
+ best_type
254
+ end
255
+
256
+ def self.hint_type(name); nil end #:nodoc:
257
+
258
+ private
259
+
260
+ def cache_key(key)
261
+ @names_cache.push(key)
262
+ end
263
+
264
+ def cache_value(value)
265
+ case value
266
+ when String then @strings_cache.push(value)
267
+ when Name then @names_cache.push(value)
268
+ when Reference then
269
+ (@xref_cache[value] ||= []).push(self)
270
+ when Dictionary, Array
271
+ @strings_cache.concat(value.strings_cache)
272
+ @names_cache.concat(value.names_cache)
273
+ @xref_cache.update(value.xref_cache) do |_ref, cache1, cache2|
274
+ cache1.concat(cache2)
275
+ end
276
+
277
+ value.strings_cache.clear
278
+ value.names_cache.clear
279
+ value.xref_cache.clear
280
+ end
281
+ end
282
+
283
+ def guess_value_type(key, value)
284
+ hint_type = self.class.hint_type(key.value)
285
+ if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
286
+ hint_type = hint_type.find {|type| type < value.class }
287
+ end
288
+
289
+ hint_type
290
+ end
291
+ end
268
292
 
269
- end #class
270
-
271
293
  end
272
- # Origami