Floppy-amee 0.4.33 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/amee.rb CHANGED
@@ -11,6 +11,18 @@ class String
11
11
  def is_json?
12
12
  slice(0,1) == '{'
13
13
  end
14
+ def is_v2_json?
15
+ is_json? && match('"apiVersion".*?:.*?"2.0"')
16
+ end
17
+ def is_xml?
18
+ slice(0,5) == '<?xml'
19
+ end
20
+ def is_v2_xml?
21
+ is_xml? && include?('<Resources xmlns="http://schemas.amee.cc/2.0">')
22
+ end
23
+ def is_v2_atom?
24
+ is_xml? && (include?('<feed ') || include?('<entry ')) && include?('xmlns:amee="http://schemas.amee.cc/2.0"')
25
+ end
14
26
  end
15
27
 
16
28
  require 'amee/version'
@@ -25,4 +37,28 @@ require 'amee/data_item_value'
25
37
  require 'amee/profile'
26
38
  require 'amee/profile_category'
27
39
  require 'amee/profile_item'
28
- require 'amee/drill_down'
40
+ require 'amee/drill_down'
41
+
42
+ class Date
43
+ def amee2schema
44
+ strftime("%Y-%m-%dT%H:%M+0000")
45
+ end
46
+ def amee1_date
47
+ strftime("%Y%m%d")
48
+ end
49
+ def amee1_month
50
+ strftime("%Y%m")
51
+ end
52
+ end
53
+
54
+ class Time
55
+ def amee2schema
56
+ strftime("%Y-%m-%dT%H:%M+0000")
57
+ end
58
+ def amee1_date
59
+ strftime("%Y%m%d")
60
+ end
61
+ def amee1_month
62
+ strftime("%Y%m")
63
+ end
64
+ end
@@ -3,24 +3,29 @@ require 'net/http'
3
3
  module AMEE
4
4
  class Connection
5
5
 
6
- def initialize(server, username = nil, password = nil, use_json_if_available = true, enable_caching = false, enable_debug = false)
6
+ def initialize(server, username, password, options = {})
7
+ unless options.is_a?(Hash)
8
+ raise AMEE::ArgumentError.new("Fourth argument must be a hash of options!")
9
+ end
7
10
  @server = server
8
11
  @username = username
9
12
  @password = password
10
13
  @auth_token = nil
11
- @use_json_if_available = use_json_if_available
12
- if (@username || @password) && !valid?
13
- raise "Must specify both username and password for authenticated access"
14
+ @format = options[:format] || (defined?(JSON) ? :json : :xml)
15
+ if !valid?
16
+ raise "You must supply connection details - server, username and password are all required!"
14
17
  end
15
- @enable_caching = enable_caching
18
+ @enable_caching = options[:enable_caching]
16
19
  if @enable_caching
17
20
  $cache ||= {}
18
21
  end
19
22
  # Make connection to server
20
23
  @http = Net::HTTP.new(@server)
21
24
  @http.read_timeout = 5
22
- @http.set_debug_output($stdout) if enable_debug
25
+ @http.set_debug_output($stdout) if options[:enable_debug]
23
26
  end
27
+
28
+ attr_reader :format
24
29
 
25
30
  def timeout
26
31
  @http.read_timeout
@@ -30,19 +35,21 @@ module AMEE
30
35
  @http.read_timeout = t
31
36
  end
32
37
 
38
+ def version
39
+ @version
40
+ end
41
+
33
42
  def valid?
34
- !((@username || @password) ? (@username.nil? || @password.nil? || @server.nil?) : @server.nil?)
43
+ @username && @password && @server
35
44
  end
36
45
 
37
- def can_authenticate?
38
- !(@username.nil? || @password.nil?)
39
- end
40
-
41
46
  def authenticated?
42
47
  !@auth_token.nil?
43
48
  end
44
49
 
45
50
  def get(path, data = {})
51
+ # Allow format override
52
+ format = data.delete(:format) || @format
46
53
  # Create URL parameters
47
54
  params = []
48
55
  data.each_pair do |key, value|
@@ -53,12 +60,15 @@ module AMEE
53
60
  end
54
61
  # Send request
55
62
  return $cache[path] if @enable_caching and $cache[path]
56
- response = do_request Net::HTTP::Get.new(path)
63
+ response = do_request(Net::HTTP::Get.new(path), format)
57
64
  $cache[path] = response if @enable_caching
58
65
  return response
59
66
  end
60
67
 
61
68
  def post(path, data = {})
69
+ # Allow format override
70
+ format = data.delete(:format) || @format
71
+ # Clear cache
62
72
  clear_cache
63
73
  # Create POST request
64
74
  post = Net::HTTP::Post.new(path)
@@ -68,10 +78,13 @@ module AMEE
68
78
  end
69
79
  post.body = body.join '&'
70
80
  # Send request
71
- do_request(post)
81
+ do_request(post, format)
72
82
  end
73
83
 
74
84
  def put(path, data = {})
85
+ # Allow format override
86
+ format = data.delete(:format) || @format
87
+ # Clear cache
75
88
  clear_cache
76
89
  # Create PUT request
77
90
  put = Net::HTTP::Put.new(path)
@@ -81,7 +94,7 @@ module AMEE
81
94
  end
82
95
  put.body = body.join '&'
83
96
  # Send request
84
- do_request(put)
97
+ do_request(put, format)
85
98
  end
86
99
 
87
100
  def delete(path)
@@ -93,24 +106,36 @@ module AMEE
93
106
  end
94
107
 
95
108
  def authenticate
96
- unless can_authenticate?
97
- raise AMEE::AuthRequired.new("Authentication required. Please provide your username and password.")
98
- end
99
109
  response = nil
100
- post = Net::HTTP::Post.new("/auth")
110
+ post = Net::HTTP::Post.new("/auth/signIn")
101
111
  post.body = "username=#{@username}&password=#{@password}"
102
- post['Accept'] = content_type
112
+ post['Accept'] = content_type(:xml)
103
113
  response = @http.request(post)
104
114
  @auth_token = response['authToken']
105
115
  unless authenticated?
106
116
  raise AMEE::AuthFailed.new("Authentication failed. Please check your username and password.")
107
117
  end
118
+ # Detect API version
119
+ if response.body.is_json?
120
+ @version = JSON.parse(response.body)["user"]["apiVersion"].to_f
121
+ elsif response.body.is_xml?
122
+ @version = REXML::Document.new(response.body).elements['Resources'].elements['SignInResource'].elements['User'].elements['ApiVersion'].text.to_f
123
+ else
124
+ @version = 1.0
125
+ end
108
126
  end
109
127
 
110
128
  protected
111
129
 
112
- def content_type
113
- (@use_json_if_available && defined?(JSON)) ? 'application/json' : 'application/xml'
130
+ def content_type(format = @format)
131
+ case format
132
+ when :xml
133
+ return 'application/xml'
134
+ when :json
135
+ return 'application/json'
136
+ when :atom
137
+ return 'application/atom+xml'
138
+ end
114
139
  end
115
140
 
116
141
  def redirect?(response)
@@ -122,21 +147,21 @@ module AMEE
122
147
  when '200'
123
148
  return true
124
149
  when '403'
125
- raise AMEE::PermissionDenied.new("You do not have permission to perform the requested operation")
150
+ raise AMEE::PermissionDenied.new("You do not have permission to perform the requested operation. AMEE Response: #{response.body}")
126
151
  when '401'
127
152
  authenticate
128
153
  return false
129
154
  else
130
- raise AMEE::UnknownError.new("An error occurred while talking to AMEE: HTTP response code #{response.code}")
155
+ raise AMEE::UnknownError.new("An error occurred while talking to AMEE: HTTP response code #{response.code}. AMEE Response: #{response.body}")
131
156
  end
132
157
  end
133
158
 
134
- def do_request(request)
159
+ def do_request(request, format = @format)
135
160
  # Open HTTP connection
136
161
  @http.start
137
162
  # Do request
138
163
  begin
139
- response = send_request(request)
164
+ response = send_request(request, format)
140
165
  end while !response_ok?(response)
141
166
  # Return body of response
142
167
  return response.body
@@ -147,9 +172,9 @@ module AMEE
147
172
  @http.finish if @http.started?
148
173
  end
149
174
 
150
- def send_request(request)
175
+ def send_request(request, format = @format)
151
176
  request['authToken'] = @auth_token
152
- request['Accept'] = content_type
177
+ request['Accept'] = content_type(format)
153
178
  response = @http.request(request)
154
179
  # Handle 404s
155
180
  if response.code == '404'
@@ -1,14 +1,14 @@
1
1
  module AMEE
2
2
 
3
+ class ArgumentError < Exception
4
+ end
5
+
3
6
  class BadData < Exception
4
7
  end
5
8
 
6
9
  class AuthFailed < Exception
7
10
  end
8
11
 
9
- class AuthRequired < Exception
10
- end
11
-
12
12
  class PermissionDenied < Exception
13
13
  end
14
14
 
@@ -7,13 +7,15 @@ module AMEE
7
7
  def initialize(data = {})
8
8
  @children = data ? data[:children] : []
9
9
  @items = data ? data[:items] : []
10
- @total_amount_per_month = data[:total_amount_per_month]
10
+ @total_amount = data[:total_amount]
11
+ @total_amount_unit = data[:total_amount_unit]
11
12
  super
12
13
  end
13
14
 
14
15
  attr_reader :children
15
16
  attr_reader :items
16
- attr_reader :total_amount_per_month
17
+ attr_reader :total_amount
18
+ attr_reader :total_amount_unit
17
19
 
18
20
  def self.parse_json_profile_item(item)
19
21
  item_data = {}
@@ -22,14 +24,33 @@ module AMEE
22
24
  case key
23
25
  when 'dataItemLabel', 'dataItemUid', 'name', 'path', 'uid'
24
26
  item_data[key.to_sym] = value
27
+ when 'dataItem'
28
+ item_data[:dataItemLabel] = value['Label']
29
+ item_data[:dataItemUid] = value['uid']
25
30
  when 'created', 'modified', 'label' # ignore these
26
31
  nil
27
32
  when 'validFrom'
28
33
  item_data[:validFrom] = DateTime.strptime(value, "%Y%m%d")
34
+ when 'startDate'
35
+ item_data[:startDate] = DateTime.parse(value)
36
+ when 'endDate'
37
+ item_data[:endDate] = DateTime.parse(value) rescue nil
29
38
  when 'end'
30
39
  item_data[:end] = (value == "true")
31
40
  when 'amountPerMonth'
32
41
  item_data[:amountPerMonth] = value.to_f
42
+ when 'amount'
43
+ item_data[:amount] = value['value'].to_f
44
+ item_data[:amount_unit] = value['unit']
45
+ when 'itemValues'
46
+ value.each do |itemval|
47
+ path = itemval['path'].to_sym
48
+ item_data[:values][path.to_sym] = {}
49
+ item_data[:values][path.to_sym][:name] = itemval['name']
50
+ item_data[:values][path.to_sym][:value] = itemval['value']
51
+ item_data[:values][path.to_sym][:unit] = itemval['unit']
52
+ item_data[:values][path.to_sym][:per_unit] = itemval['perUnit']
53
+ end
33
54
  else
34
55
  item_data[:values][key.to_sym] = value
35
56
  end
@@ -72,7 +93,8 @@ module AMEE
72
93
  data[:profile_date] = DateTime.strptime(doc['profileDate'], "%Y%m")
73
94
  data[:name] = doc['dataCategory']['name']
74
95
  data[:path] = doc['path']
75
- data[:total_amount_per_month] = doc['totalAmountPerMonth']
96
+ data[:total_amount] = doc['totalAmountPerMonth']
97
+ data[:total_amount_unit] = "kg/month"
76
98
  data[:children] = []
77
99
  if doc['children'] && doc['children']['dataCategories']
78
100
  doc['children']['dataCategories'].each do |child|
@@ -92,6 +114,35 @@ module AMEE
92
114
  raise AMEE::BadData.new("Couldn't load ProfileCategory from JSON data. Check that your URL is correct.")
93
115
  end
94
116
 
117
+ def self.from_v2_json(json)
118
+ # Parse json
119
+ doc = JSON.parse(json)
120
+ data = {}
121
+ data[:profile_uid] = doc['profile']['uid']
122
+ #data[:profile_date] = DateTime.strptime(doc['profileDate'], "%Y%m")
123
+ data[:name] = doc['dataCategory']['name']
124
+ data[:path] = doc['path']
125
+ data[:total_amount] = doc['totalAmount']['value'].to_f rescue nil
126
+ data[:total_amount_unit] = doc['totalAmount']['unit'] rescue nil
127
+ data[:children] = []
128
+ if doc['profileCategories']
129
+ doc['profileCategories'].each do |child|
130
+ data[:children] << parse_json_profile_category(child)
131
+ end
132
+ end
133
+ data[:items] = []
134
+ profile_items = []
135
+ profile_items.concat doc['profileItems'] rescue nil
136
+ profile_items << doc['profileItem'] unless doc['profileItem'].nil?
137
+ profile_items.each do |item|
138
+ data[:items] << parse_json_profile_item(item)
139
+ end
140
+ # Create object
141
+ Category.new(data)
142
+ rescue
143
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 JSON data. Check that your URL is correct.")
144
+ end
145
+
95
146
  def self.parse_xml_profile_item(item)
96
147
  item_data = {}
97
148
  item_data[:values] = {}
@@ -149,7 +200,8 @@ module AMEE
149
200
  data[:profile_date] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/ProfileDate").text, "%Y%m")
150
201
  data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/Name').text
151
202
  data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path').text || ""
152
- data[:total_amount_per_month] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmountPerMonth').text.to_f rescue nil
203
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmountPerMonth').text.to_f rescue nil
204
+ data[:total_amount_unit] = "kg/month"
153
205
  data[:children] = []
154
206
  REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/DataCategory') do |child|
155
207
  category_data = {}
@@ -174,6 +226,135 @@ module AMEE
174
226
  raise AMEE::BadData.new("Couldn't load ProfileCategory from XML data. Check that your URL is correct.")
175
227
  end
176
228
 
229
+ def self.parse_v2_xml_profile_item(item)
230
+ item_data = {}
231
+ item_data[:values] = {}
232
+ item.elements.each do |element|
233
+ key = element.name
234
+ case key.downcase
235
+ when 'name', 'path'
236
+ item_data[key.downcase.to_sym] = element.text
237
+ when 'dataitem'
238
+ item_data[:dataItemLabel] = element.elements['Label'].text
239
+ item_data[:dataItemUid] = element.attributes['uid'].to_s
240
+ when 'validfrom'
241
+ item_data[:validFrom] = DateTime.strptime(element.text, "%Y%m%d")
242
+ when 'startdate'
243
+ item_data[:startDate] = DateTime.parse(element.text)
244
+ when 'enddate'
245
+ item_data[:endDate] = DateTime.parse(element.text) if element.text
246
+ when 'end'
247
+ item_data[:end] = (element.text == "true")
248
+ when 'amount'
249
+ item_data[:amount] = element.text.to_f
250
+ item_data[:amount_unit] = element.attributes['unit'].to_s
251
+ when 'itemvalues'
252
+ element.elements.each do |itemvalue|
253
+ path = itemvalue.elements['Path'].text
254
+ item_data[:values][path.to_sym] = {}
255
+ item_data[:values][path.to_sym][:name] = itemvalue.elements['Name'].text
256
+ item_data[:values][path.to_sym][:value] = itemvalue.elements['Value'].text || "0"
257
+ item_data[:values][path.to_sym][:unit] = itemvalue.elements['Unit'].text
258
+ item_data[:values][path.to_sym][:per_unit] = itemvalue.elements['PerUnit'].text
259
+ end
260
+ else
261
+ item_data[:values][key.to_sym] = element.text
262
+ end
263
+ end
264
+ item_data[:uid] = item.attributes['uid'].to_s
265
+ item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
266
+ return item_data
267
+ end
268
+
269
+ def self.from_v2_xml(xml)
270
+ # Parse XML
271
+ doc = REXML::Document.new(xml)
272
+ data = {}
273
+ data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/Profile/@uid").to_s
274
+ #data[:profile_date] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/ProfileDate").text, "%Y%m")
275
+ data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/Name').text
276
+ data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path').text || ""
277
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount').text.to_f rescue nil
278
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount/@unit').to_s rescue nil
279
+ data[:children] = []
280
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileCategories/DataCategory') do |child|
281
+ category_data = {}
282
+ category_data[:name] = child.elements['Name'].text
283
+ category_data[:path] = child.elements['Path'].text
284
+ category_data[:uid] = child.attributes['uid'].to_s
285
+ data[:children] << category_data
286
+ end
287
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
288
+ data[:children] << parse_xml_profile_category(child)
289
+ end
290
+ data[:items] = []
291
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItems/ProfileItem') do |item|
292
+ data[:items] << parse_v2_xml_profile_item(item)
293
+ end
294
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItem') do |item|
295
+ data[:items] << parse_v2_xml_profile_item(item)
296
+ end
297
+ # Create object
298
+ Category.new(data)
299
+ rescue
300
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 XML data. Check that your URL is correct.")
301
+ end
302
+
303
+ def self.from_v2_atom(response)
304
+ # Parse XML
305
+ doc = REXML::Document.new(response)
306
+ data = {}
307
+ data[:profile_uid] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/(.*?)/")[1]
308
+ # data[:profile_date] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/ProfileDate").text, "%Y%m")
309
+ # data[:name] = REXML::XPath.first(doc, '/feed/title').text
310
+ data[:path] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/.*?(/.*)")[1]
311
+ data[:total_amount] = REXML::XPath.first(doc, '/feed/amee:totalAmount').text.to_f rescue nil
312
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/feed/amee:totalAmount/@unit').to_s rescue nil
313
+ data[:children] = []
314
+ # REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileCategories/DataCategory') do |child|
315
+ # category_data = {}
316
+ # category_data[:name] = child.elements['Name'].text
317
+ # category_data[:path] = child.elements['Path'].text
318
+ # category_data[:uid] = child.attributes['uid'].to_s
319
+ # data[:children] << category_data
320
+ # end
321
+ # REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
322
+ # data[:children] << parse_xml_profile_category(child)
323
+ # end
324
+ data[:items] = []
325
+ REXML::XPath.each(doc, '/feed/entry') do |entry|
326
+ item = {}
327
+ item[:uid] = entry.elements['id'].text.match("urn:item:(.*)")[1]
328
+ item[:name] = entry.elements['title'].text
329
+ item[:path] = item[:uid]
330
+ # data[:dataItemLabel].should == "gas"
331
+ # data[:dataItemUid].should == "66056991EE23"
332
+ item[:amount] = entry.elements['amee:amount'].text.to_f rescue nil
333
+ item[:amount_unit] = entry.elements['amee:amount'].attributes['unit'].to_s rescue nil
334
+ item[:startDate] = DateTime.parse(entry.elements['amee:startDate'].text)
335
+ item[:endDate] = DateTime.parse(entry.elements['amee:endDate'].text) rescue nil
336
+ item[:values] = {}
337
+ entry.elements.each do |itemvalue|
338
+ if itemvalue.name == 'itemValue'
339
+ path = itemvalue.elements['link'].attributes['href'].to_s.match(".*/(.*)")[1]
340
+ x = {}
341
+ x[:path] = path
342
+ x[:name] = itemvalue.elements['amee:name'].text
343
+ x[:value] = itemvalue.elements['amee:value'].text unless itemvalue.elements['amee:value'].text == "N/A"
344
+ x[:value] ||= "0"
345
+ x[:unit] = itemvalue.elements['amee:unit'].text rescue nil
346
+ x[:per_unit] = itemvalue.elements['amee:perUnit'].text rescue nil
347
+ item[:values][path.to_sym] = x
348
+ end
349
+ end
350
+ data[:items] << item
351
+ end
352
+ # Create object
353
+ Category.new(data)
354
+ rescue
355
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 Atom data. Check that your URL is correct.")
356
+ end
357
+
177
358
  def self.get_history(connection, path, num_months, end_date = Date.today, items_per_page = 10)
178
359
  month = end_date.month
179
360
  year = end_date.year
@@ -201,9 +382,15 @@ module AMEE
201
382
 
202
383
  def self.parse(connection, response)
203
384
  # Parse data from response
204
- if response.is_json?
385
+ if response.is_v2_json?
386
+ cat = Category.from_v2_json(response)
387
+ elsif response.is_json?
205
388
  cat = Category.from_json(response)
206
- else
389
+ elsif response.is_v2_atom?
390
+ cat = Category.from_v2_atom(response)
391
+ elsif response.is_v2_xml?
392
+ cat = Category.from_v2_xml(response)
393
+ elsif response.is_xml?
207
394
  cat = Category.from_xml(response)
208
395
  end
209
396
  # Store connection in object for future use
@@ -213,10 +400,25 @@ module AMEE
213
400
  end
214
401
 
215
402
 
216
- def self.get(connection, path, for_date = Date.today, items_per_page = 10, recurse = false)
403
+ def self.get(connection, path, options = {})
404
+ unless options.is_a?(Hash)
405
+ raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
406
+ end
407
+ # Convert to AMEE options
408
+ if options[:start_date] && connection.version < 2
409
+ options[:profileDate] = options[:start_date].amee1_month
410
+ elsif options[:start_date] && connection.version >= 2
411
+ options[:startDate] = options[:start_date].amee2schema
412
+ end
413
+ options.delete(:start_date)
414
+ if options[:end_date] && connection.version >= 2
415
+ options[:endDate] = options[:end_date].amee2schema
416
+ end
417
+ options.delete(:end_date)
418
+ if options[:duration] && connection.version >= 2
419
+ options[:duration] = "PT#{options[:duration] * 86400}S"
420
+ end
217
421
  # Load data from path
218
- options = {:profileDate => for_date.strftime("%Y%m"), :itemsPerPage => items_per_page}
219
- options[:recurse] = true if recurse == true
220
422
  response = connection.get(path, options)
221
423
  return Category.parse(connection, response)
222
424
  rescue
@@ -230,7 +432,12 @@ module AMEE
230
432
  def item(options)
231
433
  # Search fields - from most specific to least specific
232
434
  item = items.find{ |x| x[:uid] == options[:uid] || x[:name] == options[:name] || x[:dataItemUid] == options[:dataItemUid] || x[:dataItemLabel] == options[:dataItemLabel] }
233
- item ? AMEE::Profile::Item.get(connection, "#{full_path}/#{item[:path]}") : nil
435
+ # Pass through some options
436
+ new_opts = {}
437
+ new_opts[:returnUnit] = options[:returnUnit] if options[:returnUnit]
438
+ new_opts[:returnPerUnit] = options[:returnPerUnit] if options[:returnPerUnit]
439
+ new_opts[:format] = options[:format] if options[:format]
440
+ item ? AMEE::Profile::Item.get(connection, "#{full_path}/#{item[:path]}", new_opts) : nil
234
441
  end
235
442
 
236
443
  end
@@ -4,19 +4,33 @@ module AMEE
4
4
 
5
5
  def initialize(data = {})
6
6
  @values = data ? data[:values] : []
7
- @total_amount_per_month = data[:total_amount_per_month]
8
- @valid_from = data[:valid_from]
7
+ @total_amount = data[:total_amount]
8
+ @total_amount_unit = data[:total_amount_unit]
9
+ @start_date = data[:start_date] || data[:valid_from]
10
+ @end_date = data[:end_date] || (data[:end] == true ? @start_date : nil )
9
11
  @data_item_uid = data[:data_item_uid]
10
- @end = data[:end]
11
12
  super
12
13
  end
13
14
 
14
15
  attr_reader :values
15
- attr_reader :total_amount_per_month
16
- attr_reader :valid_from
17
- attr_reader :end
16
+ attr_reader :total_amount
17
+ attr_reader :total_amount_unit
18
+ attr_reader :start_date
19
+ attr_reader :end_date
18
20
  attr_reader :data_item_uid
19
21
 
22
+ # V1 compatibility
23
+ def valid_from
24
+ start_date
25
+ end
26
+ def end
27
+ end_date.nil? ? false : start_date == end_date
28
+ end
29
+
30
+ def duration
31
+ end_date.nil? ? nil : (end_date - start_date).to_f
32
+ end
33
+
20
34
  def self.from_json(json)
21
35
  # Parse json
22
36
  doc = JSON.parse(json)
@@ -26,7 +40,8 @@ module AMEE
26
40
  data[:uid] = doc['profileItem']['uid']
27
41
  data[:name] = doc['profileItem']['name']
28
42
  data[:path] = doc['path']
29
- data[:total_amount_per_month] = doc['profileItem']['amountPerMonth']
43
+ data[:total_amount] = doc['profileItem']['amountPerMonth']
44
+ data[:total_amount_unit] = "kg/month"
30
45
  data[:valid_from] = DateTime.strptime(doc['profileItem']['validFrom'], "%Y%m%d")
31
46
  data[:end] = doc['profileItem']['end'] == "false" ? false : true
32
47
  data[:values] = []
@@ -46,6 +61,38 @@ module AMEE
46
61
  raise AMEE::BadData.new("Couldn't load ProfileItem from JSON data. Check that your URL is correct.")
47
62
  end
48
63
 
64
+ def self.from_v2_json(json)
65
+ # Parse json
66
+ doc = JSON.parse(json)
67
+ data = {}
68
+ data[:profile_uid] = doc['profile']['uid']
69
+ data[:data_item_uid] = doc['profileItem']['dataItem']['uid']
70
+ data[:uid] = doc['profileItem']['uid']
71
+ data[:name] = doc['profileItem']['name']
72
+ data[:path] = doc['path']
73
+ data[:total_amount] = doc['profileItem']['amount']['value'].to_f
74
+ data[:total_amount_unit] = doc['profileItem']['amount']['unit']
75
+ data[:start_date] = DateTime.parse(doc['profileItem']['startDate'])
76
+ data[:end_date] = DateTime.parse(doc['profileItem']['endDate']) rescue nil
77
+ data[:values] = []
78
+ doc['profileItem']['itemValues'].each do |item|
79
+ value_data = {}
80
+ item.each_pair do |key,value|
81
+ case key
82
+ when 'name', 'path', 'uid', 'value', 'unit'
83
+ value_data[key.downcase.to_sym] = value
84
+ when 'perUnit'
85
+ value_data[:per_unit] = value
86
+ end
87
+ end
88
+ data[:values] << value_data
89
+ end
90
+ # Create object
91
+ Item.new(data)
92
+ rescue
93
+ raise AMEE::BadData.new("Couldn't load ProfileItem from V2 JSON data. Check that your URL is correct.")
94
+ end
95
+
49
96
  def self.from_xml(xml)
50
97
  # Parse XML
51
98
  doc = REXML::Document.new(xml)
@@ -55,7 +102,8 @@ module AMEE
55
102
  data[:uid] = REXML::XPath.first(doc, "/Resources/ProfileItemResource/ProfileItem/@uid").to_s
56
103
  data[:name] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/Name').text
57
104
  data[:path] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/Path').text || ""
58
- data[:total_amount_per_month] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/AmountPerMonth').text.to_f rescue nil
105
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/AmountPerMonth').text.to_f rescue nil
106
+ data[:total_amount_unit] = "kg/month"
59
107
  data[:valid_from] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileItemResource/ProfileItem/ValidFrom").text, "%Y%m%d")
60
108
  data[:end] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/End').text == "false" ? false : true
61
109
  data[:values] = []
@@ -78,32 +126,139 @@ module AMEE
78
126
  raise AMEE::BadData.new("Couldn't load ProfileItem from XML data. Check that your URL is correct.")
79
127
  end
80
128
 
129
+ def self.from_v2_xml(xml)
130
+ # Parse XML
131
+ doc = REXML::Document.new(xml)
132
+ data = {}
133
+ data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileItemResource/Profile/@uid").to_s
134
+ data[:data_item_uid] = REXML::XPath.first(doc, "/Resources/ProfileItemResource/DataItem/@uid").to_s
135
+ data[:uid] = REXML::XPath.first(doc, "/Resources/ProfileItemResource/ProfileItem/@uid").to_s
136
+ data[:name] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/Name').text
137
+ data[:path] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/Path').text || ""
138
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/Amount').text.to_f rescue nil
139
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/Resources/ProfileItemResource/ProfileItem/Amount/@unit').to_s rescue nil
140
+ data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "/Resources/ProfileItemResource/ProfileItem/StartDate").text)
141
+ data[:end_date] = DateTime.parse(REXML::XPath.first(doc, "/Resources/ProfileItemResource/ProfileItem/EndDate").text) rescue nil
142
+ data[:values] = []
143
+ REXML::XPath.each(doc, '/Resources/ProfileItemResource/ProfileItem/ItemValues/ItemValue') do |item|
144
+ value_data = {}
145
+ item.elements.each do |element|
146
+ key = element.name
147
+ value = element.text
148
+ case key
149
+ when 'Name', 'Path', 'Value', 'Unit'
150
+ value_data[key.downcase.to_sym] = value
151
+ when 'PerUnit'
152
+ value_data[:per_unit] = value
153
+ end
154
+ end
155
+ value_data[:uid] = item.attributes['uid'].to_s
156
+ data[:values] << value_data
157
+ end
158
+ # Create object
159
+ Item.new(data)
160
+ rescue
161
+ raise AMEE::BadData.new("Couldn't load ProfileItem from V2 XML data. Check that your URL is correct.")
162
+ end
163
+
164
+ def self.from_v2_atom(response)
165
+ # Parse XML
166
+ doc = REXML::Document.new(response)
167
+ data = {}
168
+ data[:profile_uid] = REXML::XPath.first(doc, "/entry/@xml:base").to_s.match("/profiles/(.*?)/")[1]
169
+ #data[:data_item_uid] = REXML::XPath.first(doc, "/Resources/ProfileItemResource/DataItem/@uid").to_s
170
+ data[:uid] = REXML::XPath.first(doc, "/entry/id").text.match("urn:item:(.*)")[1]
171
+ data[:name] = REXML::XPath.first(doc, '/entry/title').text
172
+ data[:path] = REXML::XPath.first(doc, "/entry/@xml:base").to_s.match("/profiles/.*?(/.*)")[1]
173
+ data[:total_amount] = REXML::XPath.first(doc, '/entry/amee:amount').text.to_f rescue nil
174
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/entry/amee:amount/@unit').to_s rescue nil
175
+ data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "/entry/amee:startDate").text)
176
+ data[:end_date] = DateTime.parse(REXML::XPath.first(doc, "/entry/amee:endDate").text) rescue nil
177
+ data[:values] = []
178
+ REXML::XPath.each(doc, '/entry/amee:itemValue') do |item|
179
+ value_data = {}
180
+ value_data[:name] = item.elements['amee:name'].text
181
+ value_data[:value] = item.elements['amee:value'].text unless item.elements['amee:value'].text == "N/A"
182
+ value_data[:path] = item.elements['link'].attributes['href'].to_s
183
+ value_data[:unit] = item.elements['amee:unit'].text rescue nil
184
+ value_data[:per_unit] = item.elements['amee:perUnit'].text rescue nil
185
+ data[:values] << value_data
186
+ end
187
+ # Create object
188
+ Item.new(data)
189
+ rescue
190
+ raise AMEE::BadData.new("Couldn't load ProfileItem from V2 ATOM data. Check that your URL is correct.")
191
+ end
192
+
81
193
  def self.parse(connection, response)
82
194
  # Parse data from response
83
- if response.is_json?
84
- cat = Item.from_json(response)
195
+ if response.is_v2_json?
196
+ item = Item.from_v2_json(response)
197
+ elsif response.is_json?
198
+ item = Item.from_json(response)
199
+ elsif response.is_v2_atom?
200
+ item = Item.from_v2_atom(response)
201
+ elsif response.is_v2_xml?
202
+ item = Item.from_v2_xml(response)
85
203
  else
86
- cat = Item.from_xml(response)
204
+ item = Item.from_xml(response)
87
205
  end
88
206
  # Store connection in object for future use
89
- cat.connection = connection
207
+ item.connection = connection
90
208
  # Done
91
- return cat
209
+ return item
92
210
  end
93
211
 
94
- def self.get(connection, path, for_date = Date.today)
212
+ def self.get(connection, path, options = {})
213
+ unless options.is_a?(Hash)
214
+ raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
215
+ end
216
+ # Convert to AMEE options
217
+ if options[:start_date] && category.connection.version < 2
218
+ options[:profileDate] = options[:start_date].amee1_month
219
+ elsif options[:start_date] && category.connection.version >= 2
220
+ options[:startDate] = options[:start_date].amee2schema
221
+ end
222
+ options.delete(:start_date)
223
+ if options[:end_date] && category.connection.version >= 2
224
+ options[:endDate] = options[:end_date].amee2schema
225
+ end
226
+ options.delete(:end_date)
227
+ if options[:duration] && category.connection.version >= 2
228
+ options[:duration] = "PT#{options[:duration] * 86400}S"
229
+ end
95
230
  # Load data from path
96
- response = connection.get(path, :profileDate => for_date.strftime("%Y%m"))
231
+ response = connection.get(path, options)
97
232
  return Item.parse(connection, response)
98
233
  rescue
99
234
  raise AMEE::BadData.new("Couldn't load ProfileItem. Check that your URL is correct.")
100
235
  end
101
236
 
102
- def self.create(profile, data_item_uid, options = {})
237
+ def self.create(category, data_item_uid, options = {})
238
+ # Store format if set
239
+ format = options[:format]
240
+ unless options.is_a?(Hash)
241
+ raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
242
+ end
243
+ # Set dates
244
+ if options[:start_date] && category.connection.version < 2
245
+ options[:profileDate] = options[:start_date].amee1_month
246
+ elsif options[:start_date] && category.connection.version >= 2
247
+ options[:startDate] = options[:start_date].amee2schema
248
+ end
249
+ options.delete(:start_date)
250
+ if options[:end_date] && category.connection.version >= 2
251
+ options[:endDate] = options[:end_date].amee2schema
252
+ end
253
+ options.delete(:end_date)
254
+ if options[:duration] && category.connection.version >= 2
255
+ options[:duration] = "PT#{options[:duration] * 86400}S"
256
+ end
103
257
  # Send data to path
104
258
  options.merge! :dataItemUid => data_item_uid
105
- response = profile.connection.post(profile.full_path, options)
106
- category = Category.parse(profile.connection, response)
259
+ response = category.connection.post(category.full_path, options)
260
+ category = Category.parse(category.connection, response)
261
+ options.merge!(:format => format) if format
107
262
  return category.item(options)
108
263
  rescue
109
264
  raise AMEE::BadData.new("Couldn't create ProfileItem. Check that your information is correct.")
@@ -112,8 +267,8 @@ module AMEE
112
267
  def self.update(connection, path, options = {})
113
268
  response = connection.put(path, options)
114
269
  return Item.parse(connection, response)
115
- #rescue
116
- # raise AMEE::BadData.new("Couldn't update ProfileItem. Check that your information is correct.")
270
+ rescue
271
+ raise AMEE::BadData.new("Couldn't update ProfileItem. Check that your information is correct.")
117
272
  end
118
273
 
119
274
  def update(options = {})
data/lib/amee/version.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  module AMEE
2
2
 
3
3
  module VERSION #:nodoc:
4
- MAJOR = 0
5
- MINOR = 4
6
- TINY = 33
4
+ MAJOR = 2
5
+ MINOR = 0
6
+ TINY = 0
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
9
9
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Floppy-amee
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.33
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Smith
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-10 00:00:00 -08:00
12
+ date: 2009-02-13 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15