device_atlas 1.3.0
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/lib/device_atlas.rb +251 -0
- metadata +64 -0
data/lib/device_atlas.rb
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'JSON'
|
|
3
|
+
#
|
|
4
|
+
# JSON is a required rependency
|
|
5
|
+
# sudo gem install JSON
|
|
6
|
+
#
|
|
7
|
+
# Used to load the recognition tree and perform lookups of all properties, or
|
|
8
|
+
# individual properties. Typical usage is as follows:
|
|
9
|
+
#
|
|
10
|
+
# device_atlas = DeviceAtlas.new
|
|
11
|
+
# tree = device_atlas.getTreeFromFile("sample/DeviceAtlas.json")
|
|
12
|
+
# properties = device_atlas.getProperties(tree, "Nokia6680...")
|
|
13
|
+
# property = device_atlas.getProperty(tree, "Nokia6680...", "displayWidth")
|
|
14
|
+
#
|
|
15
|
+
# Note that you should normally use the user-agent that was received in
|
|
16
|
+
# the device's HTTP request. In a Rails environment, you would do this as follows:
|
|
17
|
+
#
|
|
18
|
+
#
|
|
19
|
+
# user_agent = request.env['HTTP_USER_AGENT']
|
|
20
|
+
# display_width = device_atlas.getPropertyAsInteger(tree, user_agent, "displayWidth")
|
|
21
|
+
#
|
|
22
|
+
# Author:: MTLD (dotMobi)
|
|
23
|
+
#
|
|
24
|
+
class DeviceAtlas
|
|
25
|
+
|
|
26
|
+
class IncorrectPropertyTypeException < StandardError; end
|
|
27
|
+
class InvalidPropertyException < StandardError; end
|
|
28
|
+
class JsonException < StandardError; end
|
|
29
|
+
class UnknownPropertyException < StandardError; end
|
|
30
|
+
|
|
31
|
+
attr_accessor :found_properties, :patricia, :matched_ua, :unmatched_ua
|
|
32
|
+
|
|
33
|
+
# Returns a tree from a JSON string
|
|
34
|
+
def getTreeFromString(string)
|
|
35
|
+
tree = JSON::Parser.new(string, :max_nesting => false).parse
|
|
36
|
+
raise(JsonException, "Unable to load Json data.") if (tree.nil? || !tree.kind_of?(Hash))
|
|
37
|
+
raise(JsonException, "Bad data loaded into the tree") unless tree.has_key?("$")
|
|
38
|
+
raise(JsonException, "DeviceAtlas json file must be v0.7 or greater. Please download a more recent version.") if(tree["$"]["Ver"].to_f < 0.7)
|
|
39
|
+
|
|
40
|
+
pr = {}
|
|
41
|
+
pn = {}
|
|
42
|
+
tree['p'].each_with_index do |key,value|
|
|
43
|
+
pr[key] = value
|
|
44
|
+
pn[key[1..key.size]] = value
|
|
45
|
+
end
|
|
46
|
+
tree['pr'] = pr
|
|
47
|
+
tree['pn'] = pn
|
|
48
|
+
self.patricia = tree
|
|
49
|
+
tree
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns a tree from a JSON file.
|
|
53
|
+
# Use an absolute path name to be sure of success if the current working directory is not clear.
|
|
54
|
+
def getTreeFromFile(filename)
|
|
55
|
+
json = File.open(filename,"r").readlines.to_s
|
|
56
|
+
getTreeFromString(json)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the revision number of the tree
|
|
60
|
+
def getTreeRevision(tree)
|
|
61
|
+
_getRevisionFromKeyword(tree['$']["Rev"])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the revision number of this API
|
|
65
|
+
def getApiRevision()
|
|
66
|
+
_getRevisionFromKeyword('$Rev: 2830 $')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns an array of known property names.
|
|
70
|
+
# Returns all properties available for all user agents in this tree, with their data type names
|
|
71
|
+
def listProperties(tree)
|
|
72
|
+
types = {:s => "string", :b =>"boolean", :i =>"integer", :d =>"date", :u =>"unknown"}
|
|
73
|
+
properties = {}
|
|
74
|
+
tree['p'].each do |property|
|
|
75
|
+
properties[property[1..property.length]] = types[property[0..0].to_sym]
|
|
76
|
+
end
|
|
77
|
+
properties
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns an array of known properties (as strings) for the user agent
|
|
81
|
+
def getProperties(tree, userAgent)
|
|
82
|
+
_getProperties(tree, userAgent, false)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns an array of known properties (as typed) for the user agent
|
|
86
|
+
def getPropertiesAsTyped(tree, userAgent)
|
|
87
|
+
_getProperties(tree, userAgent, true)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns a value for the named property for this user agent
|
|
91
|
+
def getProperty(tree, userAgent, property)
|
|
92
|
+
_getProperty(tree, userAgent, property, false)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Strongly typed property accessor.
|
|
96
|
+
# Returns a boolean property.
|
|
97
|
+
# (Throws an exception if the property is actually of another type.)
|
|
98
|
+
def getPropertyAsBoolean(tree, userAgent, property)
|
|
99
|
+
_propertyTypeCheck(tree, property, "b", "boolean")
|
|
100
|
+
_getProperty(tree, userAgent, property, true)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Strongly typed property accessor.
|
|
104
|
+
# Returns a date property.
|
|
105
|
+
# (Throws an exception if the property is actually of another type.)
|
|
106
|
+
def getPropertyAsDate(tree, userAgent, property)
|
|
107
|
+
_propertyTypeCheck(tree, property, "d", "date")
|
|
108
|
+
_getProperty(tree, userAgent, property, true)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Strongly typed property accessor.
|
|
112
|
+
# Returns an integer property.
|
|
113
|
+
# (Throws an exception if the property is actually of another type.)
|
|
114
|
+
def getPropertyAsInteger(tree, userAgent, property)
|
|
115
|
+
_propertyTypeCheck(tree, property, "i", "integer")
|
|
116
|
+
_getProperty(tree, userAgent, property, true)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Strongly typed property accessor.
|
|
120
|
+
# Returns a string property.
|
|
121
|
+
# (Throws an exception if the property is actually of another type.)
|
|
122
|
+
def getPropertyAsString(tree, userAgent, property)
|
|
123
|
+
_propertyTypeCheck(tree, property, "s", "string")
|
|
124
|
+
_getProperty(tree, userAgent, property, true)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
protected
|
|
128
|
+
# Formats the SVN revision string to return a number
|
|
129
|
+
def _getRevisionFromKeyword(keyword)
|
|
130
|
+
keyword.gsub("$","")[5..keyword.size].strip.to_i
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns an array of known properties for the user agent.
|
|
134
|
+
# Allows the values of properties to be forced to be strings.
|
|
135
|
+
def _getProperties(tree, userAgent, typedValues)
|
|
136
|
+
idProperties = []
|
|
137
|
+
matched = ""
|
|
138
|
+
sought = nil
|
|
139
|
+
self.found_properties = {}
|
|
140
|
+
|
|
141
|
+
_seekProperties(tree['t'], userAgent.strip, idProperties, sought, matched)
|
|
142
|
+
properties = {}
|
|
143
|
+
self.found_properties.each_pair do |id,value|
|
|
144
|
+
if typedValues
|
|
145
|
+
properties[_propertyFromId(tree, id)] = _valueAsTypedFromId(tree, value, id)
|
|
146
|
+
else
|
|
147
|
+
properties[_propertyFromId(tree, id)] = _valueFromId(tree, value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
properties["_matched"] = self.matched_ua
|
|
151
|
+
properties["_unmatched"] = self.unmatched_ua
|
|
152
|
+
properties
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns a value for the named property for this user agent.
|
|
156
|
+
# Allows the value to be typed or forced as a string.
|
|
157
|
+
def _getProperty(tree, userAgent, property, typedValue)
|
|
158
|
+
propertyId = _idFromProperty(tree, property)
|
|
159
|
+
idProperties = []
|
|
160
|
+
sought = {}
|
|
161
|
+
sought[propertyId.to_s] = 1
|
|
162
|
+
matched = ""
|
|
163
|
+
unmatched = ""
|
|
164
|
+
self.found_properties = {}
|
|
165
|
+
_seekProperties(tree['t'], userAgent.strip, idProperties, sought, matched)
|
|
166
|
+
|
|
167
|
+
raise(InvalidPropertyException, "The property #{property} is invalid for the User Agent:#{userAgent}") if self.found_properties.size == 0
|
|
168
|
+
|
|
169
|
+
if typedValue
|
|
170
|
+
_valueAsTypedFromId(tree, self.found_properties[propertyId.to_s], propertyId)
|
|
171
|
+
else
|
|
172
|
+
_valueFromId(tree, self.found_properties[propertyId.to_s])
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Return the coded ID for a property's name
|
|
177
|
+
def _idFromProperty(tree, property)
|
|
178
|
+
raise(UnknownPropertyException, "The property #{property} is not known in this tree.") unless tree['pn'].has_key?(property)
|
|
179
|
+
tree['pn'][property]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Return the name for a property's coded ID
|
|
183
|
+
def _propertyFromId(tree, id)
|
|
184
|
+
string = tree['p'][id.to_i]
|
|
185
|
+
return if string.nil?
|
|
186
|
+
string[1..string.size]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Checks that the property is of the supplied type or throws an error.
|
|
190
|
+
def _propertyTypeCheck(tree, property, prefix, typeName)
|
|
191
|
+
key = prefix + property
|
|
192
|
+
raise(IncorrectPropertyTypeException, "#{property} is not of type #{typeName}") unless tree['pr'].has_key?(key)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Seek properties for a user agent within a node.
|
|
196
|
+
# This is designed to be recursed, and only externally called with the node representing the top of the tree
|
|
197
|
+
def _seekProperties(node, string, properties, sought, matched)
|
|
198
|
+
unmatched = string
|
|
199
|
+
self.unmatched_ua = unmatched
|
|
200
|
+
if node.has_key?('d')
|
|
201
|
+
if (sought != nil && sought.size == 0)
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
node['d'].each do |property,value|
|
|
205
|
+
if (sought == nil || sought[property])
|
|
206
|
+
self.found_properties[property.to_s] = value
|
|
207
|
+
properties[property.to_i] = value
|
|
208
|
+
end
|
|
209
|
+
if (sought != nil) && ( !node.has_key?('m') || ( node.has_key?('m') && !node['m'].has_key?(property) ) )
|
|
210
|
+
sought.delete(property)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
if node.has_key?('c')
|
|
215
|
+
(0..string.length+1).each do |c|
|
|
216
|
+
seek = string[0..c]
|
|
217
|
+
# TODO for some reason the last node is an array? handle it better?
|
|
218
|
+
if node['c'].kind_of?(Hash) && node['c'][seek]
|
|
219
|
+
matched += seek
|
|
220
|
+
self.matched_ua = matched
|
|
221
|
+
return _seekProperties(node['c'][seek], string[c+1..string.length], properties, sought, matched)
|
|
222
|
+
elsif node['c'][seek.to_i]
|
|
223
|
+
matched += seek
|
|
224
|
+
self.matched_ua = matched
|
|
225
|
+
return _seekProperties(node['c'][seek.to_i], string[c+1..string.length], properties, sought, matched)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Returns the property value as typed
|
|
232
|
+
def _valueAsTypedFromId(tree, id, propertyId)
|
|
233
|
+
obj = tree['v'][id]
|
|
234
|
+
case tree['p'][propertyId.to_i][0..0]
|
|
235
|
+
when 's'
|
|
236
|
+
return obj.to_s
|
|
237
|
+
when 'b'
|
|
238
|
+
return (obj.to_i == 1)
|
|
239
|
+
when 'i'
|
|
240
|
+
return obj.to_i
|
|
241
|
+
when 'd'
|
|
242
|
+
return Date.parse(obj)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Return the value for a value's coded ID
|
|
247
|
+
def _valueFromId(tree, id)
|
|
248
|
+
tree['v'][id]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: device_atlas
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- dotMobi
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2010-04-28 00:00:00 +02:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: json
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 1.0.0
|
|
24
|
+
version:
|
|
25
|
+
description:
|
|
26
|
+
email: contact@mtld.mobi
|
|
27
|
+
executables: []
|
|
28
|
+
|
|
29
|
+
extensions: []
|
|
30
|
+
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
|
|
33
|
+
files:
|
|
34
|
+
- lib/device_atlas.rb
|
|
35
|
+
has_rdoc: true
|
|
36
|
+
homepage: http://mtld.mobi
|
|
37
|
+
licenses: []
|
|
38
|
+
|
|
39
|
+
post_install_message:
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: "0"
|
|
49
|
+
version:
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: "0"
|
|
55
|
+
version:
|
|
56
|
+
requirements: []
|
|
57
|
+
|
|
58
|
+
rubyforge_project:
|
|
59
|
+
rubygems_version: 1.3.5
|
|
60
|
+
signing_key:
|
|
61
|
+
specification_version: 3
|
|
62
|
+
summary: ""
|
|
63
|
+
test_files: []
|
|
64
|
+
|