Floppy-amee 0.4.33 → 2.0.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/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