active_shipping 1.0.0.pre4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +0 -1
  5. data/CHANGELOG.md +17 -0
  6. data/CONTRIBUTING.md +2 -2
  7. data/Gemfile.activesupport32 +1 -0
  8. data/README.md +1 -1
  9. data/Rakefile +1 -1
  10. data/active_shipping.gemspec +2 -2
  11. data/lib/active_shipping.rb +2 -3
  12. data/lib/active_shipping/carriers.rb +1 -0
  13. data/lib/active_shipping/carriers/canada_post_pws.rb +281 -266
  14. data/lib/active_shipping/carriers/correios.rb +285 -0
  15. data/lib/active_shipping/carriers/fedex.rb +205 -199
  16. data/lib/active_shipping/carriers/stamps.rb +218 -219
  17. data/lib/active_shipping/carriers/ups.rb +287 -48
  18. data/lib/active_shipping/carriers/usps.rb +3 -3
  19. data/lib/active_shipping/delivery_date_estimate.rb +21 -0
  20. data/lib/active_shipping/delivery_date_estimates_response.rb +11 -0
  21. data/lib/active_shipping/errors.rb +20 -1
  22. data/lib/active_shipping/location.rb +0 -5
  23. data/lib/active_shipping/response.rb +0 -15
  24. data/lib/active_shipping/version.rb +1 -1
  25. data/test/credentials.yml +11 -3
  26. data/test/fixtures/xml/correios/book_response.xml +13 -0
  27. data/test/fixtures/xml/correios/book_response_invalid.xml +13 -0
  28. data/test/fixtures/xml/correios/clothes_response.xml +43 -0
  29. data/test/fixtures/xml/correios/poster_response.xml +23 -0
  30. data/test/fixtures/xml/correios/shoes_response.xml +43 -0
  31. data/test/fixtures/xml/fedex/tracking_request.xml +13 -11
  32. data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
  33. data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
  34. data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
  35. data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
  36. data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
  37. data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
  38. data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
  39. data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
  40. data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
  41. data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
  42. data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
  43. data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
  44. data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -1
  45. data/test/fixtures/xml/usps/delivered_extended_tracking_response.xml +11 -0
  46. data/test/remote/canada_post_pws_platform_test.rb +35 -22
  47. data/test/remote/canada_post_pws_test.rb +32 -40
  48. data/test/remote/correios_test.rb +83 -0
  49. data/test/remote/fedex_test.rb +95 -13
  50. data/test/remote/stamps_test.rb +1 -0
  51. data/test/remote/ups_test.rb +77 -40
  52. data/test/remote/usps_test.rb +13 -1
  53. data/test/test_helper.rb +12 -2
  54. data/test/unit/carriers/canada_post_pws_rating_test.rb +66 -59
  55. data/test/unit/carriers/canada_post_pws_shipping_test.rb +34 -23
  56. data/test/unit/carriers/correios_test.rb +244 -0
  57. data/test/unit/carriers/fedex_test.rb +161 -156
  58. data/test/unit/carriers/ups_test.rb +193 -1
  59. data/test/unit/carriers/usps_test.rb +14 -0
  60. data/test/unit/location_test.rb +0 -10
  61. metadata +63 -46
  62. metadata.gz.sig +0 -0
  63. data/lib/vendor/test_helper.rb +0 -6
  64. data/lib/vendor/xml_node/README +0 -36
  65. data/lib/vendor/xml_node/Rakefile +0 -21
  66. data/lib/vendor/xml_node/benchmark/bench_generation.rb +0 -30
  67. data/lib/vendor/xml_node/init.rb +0 -1
  68. data/lib/vendor/xml_node/lib/xml_node.rb +0 -221
  69. data/lib/vendor/xml_node/test/test_generating.rb +0 -89
  70. data/lib/vendor/xml_node/test/test_parsing.rb +0 -40
  71. data/test/fixtures/xml/fedex/tracking_response.xml +0 -151
  72. data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +0 -76
  73. data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +0 -139
  74. data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +0 -150
  75. data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +0 -95
  76. data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce37970d12bcb0e0ad9b464cdf4f772a69dc77b6
4
- data.tar.gz: fbc3624b76f5ae90378d39bb6dcdd348466bafc2
3
+ metadata.gz: d67ea13d5af138aa5a66c9cdf708edae95282d94
4
+ data.tar.gz: 061f00b789943cfb9def0ca495315a6d0dd482a3
5
5
  SHA512:
6
- metadata.gz: 1326d49a7cd30d4c77473c479aa5fe0da42d6fa861bd061476ce90a63ccdd0360d5dac556fb28896fccdb73529fa9331e43925eb13f660842aaa0ae710d113cd
7
- data.tar.gz: 409147bef83cc2e1b2296a96c410cbc03df24fa84bddf20d4612ee40f29789cd0355ff9629522a720a228cfa9b004cf6fc46b26ba0ff434dbf44a5bb3bc5ddce
6
+ metadata.gz: a426a84ca1c081e64b1bcffa792ccfbecc594648f14272ec6c00385fe4ce6fbab54c9cc9ec1505059dc7cca8b0604cb2faaf9bbbc55a647c728239137e95fa0d
7
+ data.tar.gz: 46d1e8c45a4c5fc7594626c1bf8000db652cf5a88cbd49e992323b0de9c2b597d62988bfb93991da6a90a09fae36e1f5c27aef224d038a4f02464b428eb0e5a7
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/.yardopts CHANGED
@@ -6,7 +6,6 @@
6
6
  --no-private
7
7
  --markup markdown
8
8
  --markup-provider redcarpet
9
- --exclude /lib/vendor/xml_node/
10
9
  -
11
10
  README.md
12
11
  CHANGELOG.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # ActiveShipping CHANGELOG
2
2
 
3
+ ### v1.0.0
4
+
5
+ - **BREAKING CHANGE:** Change namespace from `ActiveMerchant::Shipping` to `ActiveShipping`
6
+ - Drop support for Ruby 1.8 and Ruby 1.9.
7
+ - Drop support for ActiveSupport < 3.2, support up to ActiveSupport 4.2.
8
+ - Updated Fedex to use latest API version for tracking
9
+ - Various improvements to UPS carrier implementation.
10
+ - Small bugfixes in USPS carrier implementation.
11
+ - Various small bugfixes in XML handling for several carriers.
12
+ - Rewite all carriers to use nokogiri for XML parsing and generating.
13
+ - Bump `active_utils` dependency to require 3.x to avoid conflicts with `ActiveMerchant`.
14
+ - Extracted `quantified` into separate gem.
15
+ - Removed vendored `XmlNode` library.
16
+ - Removed `builder` dependency.
17
+ - Improved test setup that allows running functional tests on CI.
18
+ - Improved documentation of the abstraction API.
19
+
3
20
  ### v0.10.1
4
21
 
5
22
  - Canada Post PWS: Makes wrapper act more consistently with the rest of the API [jnormore]
data/CONTRIBUTING.md CHANGED
@@ -9,7 +9,7 @@ Please use clean, concise code that follows Ruby community standards. For exampl
9
9
  - Be consistent
10
10
  - Don't use too much white space
11
11
  - Use 2 space indent, no tabs.
12
- - No spaces after (, [ and before ],)
12
+ - No spaces after `(`, `[` and before `]`, `)`
13
13
  - Nor too little
14
14
  - Use spaces around operators and after commas, colons and semicolons
15
15
  - Indent when as deep as case
@@ -19,7 +19,7 @@ Please use clean, concise code that follows Ruby community standards. For exampl
19
19
 
20
20
  - Add unit tests, and remote tests to make sure we won't introduce regressions to your code later on.
21
21
  - Make sure CI passes for all Ruby versions and dependency versions we support.
22
- - XML handling: use `REXML` for parsing XML, and `builder` to generate it.
22
+ - XML handling: use `Nokogiri.XML` for parsing XML, and `Nokogiri::XML::Builder` to generate it.
23
23
  - JSON: use the JSON module that is included in Rubys standard ibrary
24
24
  - HTTP: use `ActiveUtils`'s `PostsData`.
25
25
  - Do not add new gem dependencies.
@@ -2,3 +2,4 @@ source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
4
  gem 'activesupport', '~> 3.2.0'
5
+ gem 'tzinfo'
data/README.md CHANGED
@@ -97,7 +97,7 @@ Active Shipping is currently being used and improved in a production environment
97
97
 
98
98
  ## Running the tests
99
99
 
100
- After installing dependencies with `bundle install`, you can run the unit tests with `rake test:units` and the remote tests with `rake test:remote`. The unit tests mock out requests and responses so that everything runs locally, while the remote tests actually hit the carrier servers. For the remote tests, you'll need valid test credentials for any carriers' tests you want to run. The credentials should go in ~/.active_shipping/credentials.yml, and the format of that file can be seen in the included [credentials.yml](https://github.com/Shopify/active_shipping/blob/master/test/credentials.yml).
100
+ After installing dependencies with `bundle install`, you can run the unit tests with `rake test:units` and the remote tests with `rake test:remote`. The unit tests mock out requests and responses so that everything runs locally, while the remote tests actually hit the carrier servers. For the remote tests, you'll need valid test credentials for any carriers' tests you want to run. The credentials should go in ~/.active_shipping/credentials.yml, and the format of that file can be seen in the included [credentials.yml](https://github.com/Shopify/active_shipping/blob/master/test/credentials.yml). For some carriers, we have public credentials you can use for testing: see `.travis.yml`.
101
101
 
102
102
  ## Development
103
103
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
 
4
- desc "Run the unit and functioknal remote tests"
4
+ desc "Run the unit and functional remote tests"
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "test"
7
7
  t.pattern = 'test/**/*_test.rb'
@@ -14,16 +14,16 @@ Gem::Specification.new do |s|
14
14
  s.description = "Get rates and tracking info from various shipping carriers. Extracted from Shopify."
15
15
  s.license = 'MIT'
16
16
 
17
- s.add_dependency('quantified', '~> 1.0')
17
+ s.add_dependency('quantified', '~> 1.0.1')
18
18
  s.add_dependency('activesupport', '>= 3.2', '< 5.0.0')
19
19
  s.add_dependency('active_utils', '~> 3.0.0')
20
- s.add_dependency('builder', '>= 2.1.2', '< 4.0.0')
21
20
  s.add_dependency('nokogiri', '>= 1.6')
22
21
 
23
22
  s.add_development_dependency('minitest')
24
23
  s.add_development_dependency('rake')
25
24
  s.add_development_dependency('mocha', '~> 1')
26
25
  s.add_development_dependency('timecop')
26
+ s.add_development_dependency('business_time')
27
27
 
28
28
  s.files = `git ls-files`.split($/)
29
29
  s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -27,20 +27,19 @@ require 'active_support/core_ext/enumerable'
27
27
  require 'active_support/core_ext/class/attribute_accessors'
28
28
  require 'active_utils'
29
29
 
30
- require 'rexml/document'
31
30
  require 'nokogiri'
32
31
  require 'quantified'
33
32
 
34
- require 'vendor/xml_node/lib/xml_node'
35
-
36
33
  require 'active_shipping/response'
37
34
  require 'active_shipping/rate_response'
38
35
  require 'active_shipping/tracking_response'
36
+ require 'active_shipping/delivery_date_estimates_response'
39
37
  require 'active_shipping/shipping_response'
40
38
  require 'active_shipping/label_response'
41
39
  require 'active_shipping/package'
42
40
  require 'active_shipping/location'
43
41
  require 'active_shipping/rate_estimate'
42
+ require 'active_shipping/delivery_date_estimate'
44
43
  require 'active_shipping/shipment_event'
45
44
  require 'active_shipping/shipment_packer'
46
45
  require 'active_shipping/carrier'
@@ -31,3 +31,4 @@ ActiveShipping::Carriers.register :CanadaPost, 'active_shipping/carriers/c
31
31
  ActiveShipping::Carriers.register :NewZealandPost, 'active_shipping/carriers/new_zealand_post'
32
32
  ActiveShipping::Carriers.register :CanadaPostPWS, 'active_shipping/carriers/canada_post_pws'
33
33
  ActiveShipping::Carriers.register :Stamps, 'active_shipping/carriers/stamps'
34
+ ActiveShipping::Carriers.register :Correios, 'active_shipping/carriers/correios'
@@ -158,13 +158,14 @@ module ActiveShipping
158
158
  # service discovery
159
159
 
160
160
  def parse_services_response(response)
161
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
162
- service_nodes = doc.elements['services'].elements.collect('service') { |node| node }
161
+ doc = Nokogiri.XML(response)
162
+ doc.remove_namespaces!
163
+ service_nodes = doc.xpath('services/service')
163
164
  service_nodes.inject({}) do |result, node|
164
- service_code = node.get_text("service-code").to_s
165
- service_name = node.get_text("service-name").to_s
166
- service_link = node.elements["link"].attributes['href']
167
- service_link_media_type = node.elements["link"].attributes['media-type']
165
+ service_code = node.at("service-code").text
166
+ service_name = node.at("service-name").text
167
+ service_link = node.at("link").attributes['href'].value
168
+ service_link_media_type = node.at("link").attributes['media-type'].value
168
169
  result[service_code] = {
169
170
  :name => service_name,
170
171
  :link => service_link,
@@ -175,36 +176,35 @@ module ActiveShipping
175
176
  end
176
177
 
177
178
  def parse_service_options_response(response)
178
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
179
- service_node = doc.elements['service']
180
- service_code = service_node.get_text("service-code").to_s
181
- service_name = service_node.get_text("service-name").to_s
182
- options_node = service_node.elements['options']
183
- unless options_node.blank?
184
- option_nodes = options_node.elements.collect('option') { |node| node }
185
- options = option_nodes.inject([]) do |result, node|
186
- option = {
187
- :code => node.get_text("option-code").to_s,
188
- :name => node.get_text("option-name").to_s,
189
- :required => node.get_text("mandatory").to_s == "false" ? false : true,
190
- :qualifier_required => node.get_text("qualifier-required").to_s == "false" ? false : true
191
- }
192
- option[:qualifier_max] = node.get_text("qualifier-max").to_s.to_i if node.get_text("qualifier-max")
193
- result << option
194
- result
195
- end
179
+ doc = Nokogiri.XML(response)
180
+ doc.remove_namespaces!
181
+
182
+ service_code = doc.root.at("service-code").text
183
+ service_name = doc.root.at("service-name").text
184
+
185
+ option_nodes = doc.root.xpath('options/option')
186
+ options = option_nodes.map do |node|
187
+ option = {
188
+ :code => node.at("option-code").text,
189
+ :name => node.at("option-name").text,
190
+ :required => node.at("mandatory").text != "false",
191
+ :qualifier_required => node.at("qualifier-required").text != "false",
192
+ }
193
+ option[:qualifier_max] = node.at("qualifier-max").text.to_i if node.at("qualifier-max")
194
+ option
196
195
  end
197
- restrictions_node = service_node.elements['restrictions']
198
- dimensions_node = restrictions_node.elements['dimensional-restrictions']
196
+
197
+ restrictions_node = doc.root.at('restrictions')
198
+ dimensions_node = restrictions_node.at('dimensional-restrictions')
199
199
  restrictions = {
200
- :min_weight => restrictions_node.elements["weight-restriction"].attributes['min'].to_i,
201
- :max_weight => restrictions_node.elements["weight-restriction"].attributes['max'].to_i,
202
- :min_length => dimensions_node.elements["length"].attributes['min'].to_f,
203
- :max_length => dimensions_node.elements["length"].attributes['max'].to_f,
204
- :min_height => dimensions_node.elements["height"].attributes['min'].to_f,
205
- :max_height => dimensions_node.elements["height"].attributes['max'].to_f,
206
- :min_width => dimensions_node.elements["width"].attributes['min'].to_f,
207
- :max_width => dimensions_node.elements["width"].attributes['max'].to_f
200
+ :min_weight => restrictions_node.at("weight-restriction").attributes['min'].value.to_i,
201
+ :max_weight => restrictions_node.at("weight-restriction").attributes['max'].value.to_i,
202
+ :min_length => dimensions_node.at("length").attributes['min'].value.to_f,
203
+ :max_length => dimensions_node.at("length").attributes['max'].value.to_f,
204
+ :min_height => dimensions_node.at("height").attributes['min'].value.to_f,
205
+ :max_height => dimensions_node.at("height").attributes['max'].value.to_f,
206
+ :min_width => dimensions_node.at("width").attributes['min'].value.to_f,
207
+ :max_width => dimensions_node.at("width").attributes['max'].value.to_f
208
208
  }
209
209
 
210
210
  {
@@ -216,21 +216,22 @@ module ActiveShipping
216
216
  end
217
217
 
218
218
  def parse_option_response(response)
219
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
220
- option_node = doc.elements['option']
221
- conflicts = option_node.elements['conflicting-options'].elements.collect('option-code') { |node| node.get_text.to_s } unless option_node.elements['conflicting-options'].blank?
222
- prereqs = option_node.elements['prerequisite-options'].elements.collect('option-code') { |node| node.get_text.to_s } unless option_node.elements['prerequisite-options'].blank?
219
+ doc = Nokogiri.XML(response)
220
+ doc.remove_namespaces!
221
+
222
+ conflicts = doc.root.xpath('conflicting-options/option-code').map(&:text)
223
+ prereqs = doc.root.xpath('prerequisite-options/option-code').map(&:text)
223
224
  option = {
224
- :code => option_node.get_text('option-code').to_s,
225
- :name => option_node.get_text('option-name').to_s,
226
- :class => option_node.get_text('option-class').to_s,
227
- :prints_on_label => option_node.get_text('prints-on-label').to_s == "false" ? false : true,
228
- :qualifier_required => option_node.get_text('qualifier-required').to_s == "false" ? false : true
225
+ :code => doc.root.at('option-code').text,
226
+ :name => doc.root.at('option-name').text,
227
+ :class => doc.root.at('option-class').text,
228
+ :prints_on_label => doc.root.at('prints-on-label').text != "false",
229
+ :qualifier_required => doc.root.at('qualifier-required').text != "false",
229
230
  }
230
231
  option[:conflicting_options] = conflicts if conflicts
231
232
  option[:prerequisite_options] = prereqs if prereqs
232
233
 
233
- option[:qualifier_max] = option_node.get_text("qualifier-max").to_s.to_i if option_node.get_text("qualifier-max")
234
+ option[:qualifier_max] = doc.root.at("qualifier-max").text.to_i if doc.root.at("qualifier-max")
234
235
  option
235
236
  end
236
237
 
@@ -238,30 +239,32 @@ module ActiveShipping
238
239
 
239
240
  def build_rates_request(origin, destination, line_items = [], options = {}, package = nil, services = [])
240
241
  line_items = Array(line_items)
241
- xml = XmlNode.new('mailing-scenario', :xmlns => "http://www.canadapost.ca/ws/ship/rate") do |node|
242
- node << customer_number_node(options)
243
- node << contract_id_node(options)
244
- node << quote_type_node(options)
245
- node << expected_mailing_date_node(shipping_date(options)) if options[:shipping_date]
246
- options_node = shipping_options_node(RATES_OPTIONS, options)
247
- node << options_node if options_node && !options_node.children.count.zero?
248
- node << parcel_node(line_items, package)
249
- node << origin_node(origin)
250
- node << destination_node(destination)
251
- node << services_node(services) unless services.blank?
242
+
243
+ builder = Nokogiri::XML::Builder.new do |xml|
244
+ xml.public_send('mailing-scenario', :xmlns => "http://www.canadapost.ca/ws/ship/rate") do
245
+ customer_number_node(xml, options)
246
+ contract_id_node(xml, options)
247
+ quote_type_node(xml, options)
248
+ expected_mailing_date_node(xml, shipping_date(options)) if options[:shipping_date]
249
+ shipping_options_node(xml, RATES_OPTIONS, options)
250
+ parcel_node(xml, line_items, package)
251
+ origin_node(xml, origin)
252
+ destination_node(xml, destination)
253
+ services_node(xml, services) unless services.blank?
254
+ end
252
255
  end
253
- xml.to_s
256
+ builder.to_xml
254
257
  end
255
258
 
256
259
  def parse_rates_response(response, origin, destination)
257
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
258
- raise ActiveShipping::ResponseError, "No Quotes" unless doc.elements['price-quotes']
259
-
260
- quotes = doc.elements['price-quotes'].elements.collect('price-quote') { |node| node }
261
- rates = quotes.map do |node|
262
- service_name = node.get_text("service-name").to_s
263
- service_code = node.get_text("service-code").to_s
264
- total_price = node.elements['price-details'].get_text("due").to_s
260
+ doc = Nokogiri.XML(response)
261
+ doc.remove_namespaces!
262
+ raise ActiveShipping::ResponseError, "No Quotes" unless doc.at('price-quotes')
263
+
264
+ rates = doc.root.xpath('price-quote').map do |node|
265
+ service_name = node.at("service-name").text
266
+ service_code = node.at("service-code").text
267
+ total_price = node.at('price-details/due').text
265
268
  expected_date = expected_date_from_node(node)
266
269
  options = {
267
270
  :service_code => service_code,
@@ -277,29 +280,30 @@ module ActiveShipping
277
280
  # tracking
278
281
 
279
282
  def parse_tracking_response(response)
280
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
281
- raise ActiveShipping::ResponseError, "No Tracking" unless root_node = doc.elements['tracking-detail']
283
+ doc = Nokogiri.XML(response)
284
+ doc.remove_namespaces!
285
+ raise ActiveShipping::ResponseError, "No Tracking" unless doc.at('tracking-detail')
282
286
 
283
- events = root_node.elements['significant-events'].elements.collect('occurrence') { |node| node }
287
+ events = doc.root.xpath('significant-events/occurrence')
284
288
 
285
289
  shipment_events = build_tracking_events(events)
286
- change_date = root_node.get_text('changed-expected-date').to_s
287
- expected_date = root_node.get_text('expected-delivery-date').to_s
288
- dest_postal_code = root_node.get_text('destination-postal-id').to_s
290
+ change_date = doc.root.at('changed-expected-date').text
291
+ expected_date = doc.root.at('expected-delivery-date').text
292
+ dest_postal_code = doc.root.at('destination-postal-id').text
289
293
  destination = Location.new(:postal_code => dest_postal_code)
290
- origin = Location.new(origin_hash_for(root_node))
294
+ origin = Location.new(origin_hash_for(doc.root))
291
295
  options = {
292
296
  :carrier => @@name,
293
- :service_name => root_node.get_text('service-name').to_s,
297
+ :service_name => doc.root.at('service-name').text,
294
298
  :expected_date => expected_date.blank? ? nil : Date.parse(expected_date),
295
299
  :changed_date => change_date.blank? ? nil : Date.parse(change_date),
296
- :change_reason => root_node.get_text('changed-expected-delivery-reason').to_s.strip,
297
- :destination_postal_code => root_node.get_text('destination-postal-id').to_s,
300
+ :change_reason => doc.root.at('changed-expected-delivery-reason').text.strip,
301
+ :destination_postal_code => doc.root.at('destination-postal-id').text,
298
302
  :shipment_events => shipment_events,
299
- :tracking_number => root_node.get_text('pin').to_s,
303
+ :tracking_number => doc.root.at('pin').text,
300
304
  :origin => origin,
301
305
  :destination => destination,
302
- :customer_number => root_node.get_text('mailed-by-customer-number').to_s
306
+ :customer_number => doc.root.at('mailed-by-customer-number').text
303
307
  }
304
308
 
305
309
  CPPWSTrackingResponse.new(true, "", {}, options)
@@ -307,14 +311,15 @@ module ActiveShipping
307
311
 
308
312
  def build_tracking_events(events)
309
313
  events.map do |event|
310
- date = event.get_text('event-date').to_s
311
- time = event.get_text('event-time').to_s
312
- zone = event.get_text('event-time-zone').to_s
314
+ date = event.at('event-date').text
315
+ time = event.at('event-time').text
316
+ zone = event.at('event-time-zone').text
313
317
  timestamp = DateTime.parse("#{date} #{time} #{zone}")
314
318
  time = Time.utc(timestamp.utc.year, timestamp.utc.month, timestamp.utc.day, timestamp.utc.hour, timestamp.utc.min, timestamp.utc.sec)
315
- message = event.get_text('event-description').to_s
316
- location = [event.get_text('event-retail-name'), event.get_text('event-site'), event.get_text('event-province')].compact.join(", ")
317
- name = event.get_text('event-identifier').to_s
319
+ message = event.at('event-description').text
320
+ location = [event.at('event-retail-name'), event.at('event-site'), event.at('event-province')].
321
+ reject { |e| e.nil? || e.text.empty? }.join(", ")
322
+ name = event.at('event-identifier').text
318
323
  ShipmentEvent.new(name, time, location, message)
319
324
  end
320
325
  end
@@ -332,109 +337,111 @@ module ActiveShipping
332
337
  origin = sanitize_location(origin)
333
338
  destination = sanitize_location(destination)
334
339
 
335
- xml = XmlNode.new('non-contract-shipment', :xmlns => "http://www.canadapost.ca/ws/ncshipment") do |root_node|
336
- root_node << XmlNode.new('delivery-spec') do |node|
337
- node << shipment_service_code_node(options)
338
- node << shipment_sender_node(origin, options)
339
- node << shipment_destination_node(destination, options)
340
- options_node = shipment_options_node(options)
341
- node << shipment_options_node(options) if options_node && !options_node.children.count.zero?
342
- node << shipment_parcel_node(package)
343
- node << shipment_notification_node(options)
344
- node << shipment_preferences_node(options)
345
- node << references_node(options) # optional > user defined custom notes
346
- node << shipment_customs_node(destination, line_items, options)
347
- # COD Remittance defaults to sender
340
+ builder = Nokogiri::XML::Builder.new do |xml|
341
+ xml.public_send('non-contract-shipment', :xmlns => "http://www.canadapost.ca/ws/ncshipment") do
342
+ xml.public_send('delivery-spec') do
343
+ shipment_service_code_node(xml, options)
344
+ shipment_sender_node(xml, origin, options)
345
+ shipment_destination_node(xml, destination, options)
346
+ shipment_options_node(xml, options)
347
+ shipment_parcel_node(xml, package)
348
+ shipment_notification_node(xml, options)
349
+ shipment_preferences_node(xml, options)
350
+ references_node(xml, options) # optional > user defined custom notes
351
+ shipment_customs_node(xml, destination, line_items, options)
352
+ # COD Remittance defaults to sender
353
+ end
348
354
  end
349
355
  end
350
- xml.to_s
356
+ builder.to_xml
351
357
  end
352
358
 
353
- def shipment_service_code_node(options)
354
- XmlNode.new('service-code', options[:service])
359
+ def shipment_service_code_node(xml, options)
360
+ xml.public_send('service-code', options[:service])
355
361
  end
356
362
 
357
- def shipment_sender_node(location, options)
358
- XmlNode.new('sender') do |node|
359
- node << XmlNode.new('name', location.name)
360
- node << XmlNode.new('company', location.company) if location.company.present?
361
- node << XmlNode.new('contact-phone', location.phone)
362
- node << XmlNode.new('address-details') do |innernode|
363
- innernode << XmlNode.new('address-line-1', location.address1)
364
- innernode << XmlNode.new('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
365
- innernode << XmlNode.new('city', location.city)
366
- innernode << XmlNode.new('prov-state', location.province)
367
- # innernode << XmlNode.new('country-code', location.country_code)
368
- innernode << XmlNode.new('postal-zip-code', location.postal_code)
363
+ def shipment_sender_node(xml, location, options)
364
+ xml.public_send('sender') do
365
+ xml.public_send('name', location.name)
366
+ xml.public_send('company', location.company) if location.company.present?
367
+ xml.public_send('contact-phone', location.phone)
368
+ xml.public_send('address-details') do
369
+ xml.public_send('address-line-1', location.address1)
370
+ xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
371
+ xml.public_send('city', location.city)
372
+ xml.public_send('prov-state', location.province)
373
+ # xml.public_send('country-code', location.country_code)
374
+ xml.public_send('postal-zip-code', location.postal_code)
369
375
  end
370
376
  end
371
377
  end
372
378
 
373
- def shipment_destination_node(location, options)
374
- XmlNode.new('destination') do |node|
375
- node << XmlNode.new('name', location.name)
376
- node << XmlNode.new('company', location.company) if location.company.present?
377
- node << XmlNode.new('client-voice-number', location.phone)
378
- node << XmlNode.new('address-details') do |innernode|
379
- innernode << XmlNode.new('address-line-1', location.address1)
380
- innernode << XmlNode.new('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
381
- innernode << XmlNode.new('city', location.city)
382
- innernode << XmlNode.new('prov-state', location.province) unless location.province.blank?
383
- innernode << XmlNode.new('country-code', location.country_code)
384
- innernode << XmlNode.new('postal-zip-code', location.postal_code)
379
+ def shipment_destination_node(xml, location, options)
380
+ xml.public_send('destination') do
381
+ xml.public_send('name', location.name)
382
+ xml.public_send('company', location.company) if location.company.present?
383
+ xml.public_send('client-voice-number', location.phone)
384
+ xml.public_send('address-details') do
385
+ xml.public_send('address-line-1', location.address1)
386
+ xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
387
+ xml.public_send('city', location.city)
388
+ xml.public_send('prov-state', location.province) unless location.province.blank?
389
+ xml.public_send('country-code', location.country_code)
390
+ xml.public_send('postal-zip-code', location.postal_code)
385
391
  end
386
392
  end
387
393
  end
388
394
 
389
- def shipment_options_node(options)
390
- shipping_options_node(SHIPPING_OPTIONS, options)
395
+ def shipment_options_node(xml, options)
396
+ shipping_options_node(xml, SHIPPING_OPTIONS, options)
391
397
  end
392
398
 
393
- def shipment_notification_node(options)
399
+ def shipment_notification_node(xml, options)
394
400
  return unless options[:notification_email]
395
- XmlNode.new('notification') do |node|
396
- node << XmlNode.new('email', options[:notification_email])
397
- node << XmlNode.new('on-shipment', true)
398
- node << XmlNode.new('on-exception', true)
399
- node << XmlNode.new('on-delivery', true)
401
+ xml.public_send('notification') do
402
+ xml.public_send('email', options[:notification_email])
403
+ xml.public_send('on-shipment', true)
404
+ xml.public_send('on-exception', true)
405
+ xml.public_send('on-delivery', true)
400
406
  end
401
407
  end
402
408
 
403
- def shipment_preferences_node(options)
404
- XmlNode.new('preferences') do |node|
405
- node << XmlNode.new('show-packing-instructions', options[:packing_instructions] || true)
406
- node << XmlNode.new('show-postage-rate', options[:show_postage_rate] || false)
407
- node << XmlNode.new('show-insured-value', true)
409
+ def shipment_preferences_node(xml, options)
410
+ xml.public_send('preferences') do
411
+ xml.public_send('show-packing-instructions', options[:packing_instructions] || true)
412
+ xml.public_send('show-postage-rate', options[:show_postage_rate] || false)
413
+ xml.public_send('show-insured-value', true)
408
414
  end
409
415
  end
410
416
 
411
- def references_node(options)
417
+ def references_node(xml, options)
412
418
  # custom values
413
- # XmlNode.new('references') do |node|
419
+ # xml.public_send('references') do
414
420
  # end
415
421
  end
416
422
 
417
- def shipment_customs_node(destination, line_items, options)
423
+ def shipment_customs_node(xml, destination, line_items, options)
418
424
  return unless destination.country_code != 'CA'
419
425
 
420
- XmlNode.new('customs') do |node|
426
+ xml.public_send('customs') do
421
427
  currency = options[:currency] || "CAD"
422
- node << XmlNode.new('currency', currency)
423
- node << XmlNode.new('conversion-from-cad', options[:conversion_from_cad].to_s) if currency != 'CAD' && options[:conversion_from_cad]
424
- node << XmlNode.new('reason-for-export', 'SOG') # SOG - Sale of Goods
425
- node << XmlNode.new('other-reason', options[:customs_other_reason]) if options[:customs_reason_for_export] && options[:customs_other_reason]
426
- node << XmlNode.new('additional-customs-info', options[:customs_addition_info]) if options[:customs_addition_info]
427
- node << XmlNode.new('sku-list') do |sku|
428
+ xml.public_send('currency', currency)
429
+ xml.public_send('conversion-from-cad', options[:conversion_from_cad].to_s) if currency != 'CAD' && options[:conversion_from_cad]
430
+ xml.public_send('reason-for-export', 'SOG') # SOG - Sale of Goods
431
+ xml.public_send('other-reason', options[:customs_other_reason]) if options[:customs_reason_for_export] && options[:customs_other_reason]
432
+ xml.public_send('additional-customs-info', options[:customs_addition_info]) if options[:customs_addition_info]
433
+ xml.public_send('sku-list') do
428
434
  line_items.each do |line_item|
429
- sku << XmlNode.new('item') do |item|
430
- item << XmlNode.new('hs-tariff-code', line_item.hs_code) if line_item.hs_code && !line_item.hs_code.empty?
431
- item << XmlNode.new('sku', line_item.sku) if line_item.sku && !line_item.sku.empty?
432
- item << XmlNode.new('customs-description', line_item.name.slice(0, 44))
433
- item << XmlNode.new('unit-weight', '%#2.3f' % sanitize_weight_kg(line_item.kg))
434
- item << XmlNode.new('customs-value-per-unit', '%.2f' % sanitize_price_from_cents(line_item.value))
435
- item << XmlNode.new('customs-number-of-units', line_item.quantity)
436
- item << XmlNode.new('country-of-origin', line_item.options[:country_of_origin]) if line_item.options && line_item.options[:country_of_origin] && !line_item.options[:country_of_origin].empty?
437
- item << XmlNode.new('province-of-origin', line_item.options[:province_of_origin]) if line_item.options && line_item.options[:province_of_origin] && !line_item.options[:province_of_origin].empty?
435
+ kg = '%#2.3f' % [sanitize_weight_kg(line_item.kg)]
436
+ xml.public_send('item') do
437
+ xml.public_send('hs-tariff-code', line_item.hs_code) if line_item.hs_code && !line_item.hs_code.empty?
438
+ xml.public_send('sku', line_item.sku) if line_item.sku && !line_item.sku.empty?
439
+ xml.public_send('customs-description', line_item.name.slice(0, 44))
440
+ xml.public_send('unit-weight', kg)
441
+ xml.public_send('customs-value-per-unit', '%.2f' % sanitize_price_from_cents(line_item.value))
442
+ xml.public_send('customs-number-of-units', line_item.quantity)
443
+ xml.public_send('country-of-origin', line_item.options[:country_of_origin]) if line_item.options && line_item.options[:country_of_origin] && !line_item.options[:country_of_origin].empty?
444
+ xml.public_send('province-of-origin', line_item.options[:province_of_origin]) if line_item.options && line_item.options[:province_of_origin] && !line_item.options[:province_of_origin].empty?
438
445
  end
439
446
  end
440
447
  end
@@ -442,99 +449,107 @@ module ActiveShipping
442
449
  end
443
450
  end
444
451
 
445
- def shipment_parcel_node(package, options = {})
452
+ def shipment_parcel_node(xml, package, options = {})
446
453
  weight = sanitize_weight_kg(package.kilograms.to_f)
447
- XmlNode.new('parcel-characteristics') do |el|
448
- el << XmlNode.new('weight', "%#2.3f" % weight)
454
+ xml.public_send('parcel-characteristics') do
455
+ xml.public_send('weight', "%#2.3f" % weight)
449
456
  pkg_dim = package.cm
450
457
  if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
451
- el << XmlNode.new('dimensions') do |dim|
452
- dim << XmlNode.new('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
453
- dim << XmlNode.new('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
454
- dim << XmlNode.new('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
458
+ xml.public_send('dimensions') do
459
+ xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
460
+ xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
461
+ xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
455
462
  end
463
+ xml.public_send('document', false)
464
+ else
465
+ xml.public_send('document', true)
456
466
  end
457
- el << XmlNode.new('document', false)
458
- el << XmlNode.new('mailing-tube', package.tube?)
459
- el << XmlNode.new('unpackaged', package.unpackaged?)
467
+
468
+ xml.public_send('mailing-tube', package.tube?)
469
+ xml.public_send('unpackaged', package.unpackaged?)
460
470
  end
461
471
  end
462
472
 
463
473
  def parse_shipment_response(response)
464
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
465
- raise ActiveShipping::ResponseError, "No Shipping" unless root_node = doc.elements['non-contract-shipment-info']
474
+ doc = Nokogiri.XML(response)
475
+ doc.remove_namespaces!
476
+ raise ActiveShipping::ResponseError, "No Shipping" unless doc.at('non-contract-shipment-info')
466
477
  options = {
467
- :shipping_id => root_node.get_text('shipment-id').to_s,
468
- :tracking_number => root_node.get_text('tracking-pin').to_s,
469
- :details_url => root_node.elements["links/link[@rel='details']"].attributes['href'],
470
- :label_url => root_node.elements["links/link[@rel='label']"].attributes['href'],
471
- :receipt_url => root_node.elements["links/link[@rel='receipt']"].attributes['href']
478
+ :shipping_id => doc.root.at('shipment-id').text,
479
+ :tracking_number => doc.root.at('tracking-pin').text,
480
+ :details_url => doc.root.at_xpath("links/link[@rel='details']")['href'],
481
+ :label_url => doc.root.at_xpath("links/link[@rel='label']")['href'],
482
+ :receipt_url => doc.root.at_xpath("links/link[@rel='receipt']")['href'],
472
483
  }
473
484
  CPPWSShippingResponse.new(true, "", {}, options)
474
485
  end
475
486
 
476
487
  def parse_register_token_response(response)
477
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
478
- raise ActiveShipping::ResponseError, "No Registration Token" unless root_node = doc.elements['token']
488
+ doc = Nokogiri.XML(response)
489
+ doc.remove_namespaces!
490
+ raise ActiveShipping::ResponseError, "No Registration Token" unless doc.at('token')
479
491
  options = {
480
- :token_id => root_node.get_text('token-id').to_s
492
+ :token_id => doc.root.at('token-id').text
481
493
  }
482
494
  CPPWSRegisterResponse.new(true, "", {}, options)
483
495
  end
484
496
 
485
497
  def parse_merchant_details_response(response)
486
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
487
- raise "No Merchant Info" unless root_node = doc.elements['merchant-info']
488
- raise "No Merchant Info" if root_node.get_text('customer-number').blank?
498
+ doc = Nokogiri.XML(response)
499
+ doc.remove_namespaces!
500
+ raise "No Merchant Info" unless doc.at('merchant-info')
501
+ raise "No Merchant Info" if doc.root.at('customer-number').blank?
489
502
  options = {
490
- :customer_number => root_node.get_text('customer-number').to_s,
491
- :contract_number => root_node.get_text('contract-number').to_s,
492
- :username => root_node.get_text('merchant-username').to_s,
493
- :password => root_node.get_text('merchant-password').to_s,
494
- :has_default_credit_card => root_node.get_text('has-default-credit-card') == 'true'
503
+ :customer_number => doc.root.at('customer-number').text,
504
+ :contract_number => doc.root.at('contract-number').text,
505
+ :username => doc.root.at('merchant-username').text,
506
+ :password => doc.root.at('merchant-password').text,
507
+ :has_default_credit_card => doc.root.at('has-default-credit-card').text == 'true'
495
508
  }
496
509
  CPPWSMerchantDetailsResponse.new(true, "", {}, options)
497
510
  end
498
511
 
499
512
  def parse_shipment_receipt_response(response)
500
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
501
- root = doc.elements['non-contract-shipment-receipt']
502
- cc_details_node = root.elements['cc-receipt-details']
503
- service_standard_node = root.elements['service-standard']
513
+ doc = Nokogiri.XML(response)
514
+ doc.remove_namespaces!
515
+ root = doc.at('non-contract-shipment-receipt')
516
+ cc_details_node = root.at('cc-receipt-details')
517
+ service_standard_node = root.at('service-standard')
504
518
  receipt = {
505
- :final_shipping_point => root.get_text("final-shipping-point").to_s,
506
- :shipping_point_name => root.get_text("shipping-point-name").to_s,
507
- :service_code => root.get_text("service-code").to_s,
508
- :rated_weight => root.get_text("rated-weight").to_s.to_f,
509
- :base_amount => root.get_text("base-amount").to_s.to_f,
510
- :pre_tax_amount => root.get_text("pre-tax-amount").to_s.to_f,
511
- :gst_amount => root.get_text("gst-amount").to_s.to_f,
512
- :pst_amount => root.get_text("pst-amount").to_s.to_f,
513
- :hst_amount => root.get_text("hst-amount").to_s.to_f,
514
- :charge_amount => cc_details_node.get_text("charge-amount").to_s.to_f,
515
- :currency => cc_details_node.get_text("currency").to_s,
516
- :expected_transit_days => service_standard_node.get_text("expected-transit-time").to_s.to_i,
517
- :expected_delivery_date => service_standard_node.get_text("expected-delivery-date").to_s
519
+ :final_shipping_point => root.at("final-shipping-point").text,
520
+ :shipping_point_name => root.at("shipping-point-name").text,
521
+ :service_code => root.at("service-code").text,
522
+ :rated_weight => root.at("rated-weight").text.to_f,
523
+ :base_amount => root.at("base-amount").text.to_f,
524
+ :pre_tax_amount => root.at("pre-tax-amount").text.to_f,
525
+ :gst_amount => root.at("gst-amount").text.to_f,
526
+ :pst_amount => root.at("pst-amount").text.to_f,
527
+ :hst_amount => root.at("hst-amount").text.to_f,
528
+ :charge_amount => cc_details_node.at("charge-amount").text.to_f,
529
+ :currency => cc_details_node.at("currency").text,
530
+ :expected_transit_days => service_standard_node.at("expected-transit-time").text.to_i,
531
+ :expected_delivery_date => service_standard_node.at("expected-delivery-date").text
518
532
  }
519
- option_nodes = root.elements['priced-options'].elements.collect('priced-option') { |node| node } unless root.elements['priced-options'].blank?
533
+ option_nodes = root.xpath('priced-options/priced-option')
520
534
 
521
- receipt[:priced_options] = if option_nodes
522
- option_nodes.inject({}) do |result, node|
523
- result[node.get_text("option-code").to_s] = node.get_text("option-price").to_s.to_f
535
+ receipt[:priced_options] = if option_nodes.length > 0
536
+ option_nodes.inject({}) do |result, node|
537
+ result[node.at("option-code").text] = node.at("option-price").text.to_f
524
538
  result
525
539
  end
526
540
  else
527
- []
541
+ {}
528
542
  end
529
543
 
530
544
  receipt
531
545
  end
532
546
 
533
547
  def error_response(response, response_klass)
534
- doc = REXML::Document.new(REXML::Text::unnormalize(response))
535
- messages = doc.elements['messages'].elements.collect('message') { |node| node }
536
- message = messages.map { |m| m.get_text('description').to_s }.join(", ")
537
- code = messages.map { |m| m.get_text('code').to_s }.join(", ")
548
+ doc = Nokogiri.XML(response)
549
+ doc.remove_namespaces!
550
+ messages = doc.xpath('messages/message')
551
+ message = messages.map { |m| m.at('description').text }.join(", ")
552
+ code = messages.map { |m| m.at('code').text }.join(", ")
538
553
  response_klass.new(false, message, {}, :carrier => @@name, :code => code)
539
554
  end
540
555
 
@@ -612,110 +627,110 @@ module ActiveShipping
612
627
  headers
613
628
  end
614
629
 
615
- def customer_number_node(options)
616
- XmlNode.new("customer-number", options[:customer_number] || customer_number)
630
+ def customer_number_node(xml, options)
631
+ xml.public_send("customer-number", options[:customer_number] || customer_number)
617
632
  end
618
633
 
619
- def contract_id_node(options)
620
- XmlNode.new("contract-id", options[:contract_id]) if options[:contract_id]
634
+ def contract_id_node(xml, options)
635
+ xml.public_send("contract-id", options[:contract_id]) if options[:contract_id]
621
636
  end
622
637
 
623
- def quote_type_node(options)
624
- XmlNode.new("quote-type", 'commercial')
638
+ def quote_type_node(xml, options)
639
+ xml.public_send("quote-type", 'commercial')
625
640
  end
626
641
 
627
- def expected_mailing_date_node(date_as_string)
628
- XmlNode.new("expected-mailing-date", date_as_string)
642
+ def expected_mailing_date_node(xml, date_as_string)
643
+ xml.public_send("expected-mailing-date", date_as_string)
629
644
  end
630
645
 
631
- def parcel_node(line_items, package = nil, options = {})
646
+ def parcel_node(xml, line_items, package = nil, options = {})
632
647
  weight = sanitize_weight_kg(package && !package.kilograms.zero? ? package.kilograms.to_f : line_items.sum(&:kilograms).to_f)
633
- XmlNode.new('parcel-characteristics') do |el|
634
- el << XmlNode.new('weight', "%#2.3f" % weight)
648
+ xml.public_send('parcel-characteristics') do
649
+ xml.public_send('weight', "%#2.3f" % weight)
635
650
  if package
636
651
  pkg_dim = package.cm
637
652
  if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
638
- el << XmlNode.new('dimensions') do |dim|
639
- dim << XmlNode.new('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
640
- dim << XmlNode.new('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
641
- dim << XmlNode.new('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
653
+ xml.public_send('dimensions') do
654
+ xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
655
+ xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
656
+ xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
642
657
  end
643
658
  end
644
659
  end
645
- el << XmlNode.new('mailing-tube', line_items.any?(&:tube?))
646
- el << XmlNode.new('oversized', true) if line_items.any?(&:oversized?)
647
- el << XmlNode.new('unpackaged', line_items.any?(&:unpackaged?))
660
+ xml.public_send('mailing-tube', line_items.any?(&:tube?))
661
+ xml.public_send('oversized', true) if line_items.any?(&:oversized?)
662
+ xml.public_send('unpackaged', line_items.any?(&:unpackaged?))
648
663
  end
649
664
  end
650
665
 
651
- def origin_node(location)
666
+ def origin_node(xml, location)
652
667
  origin = sanitize_location(location)
653
- XmlNode.new("origin-postal-code", origin.zip)
668
+ xml.public_send("origin-postal-code", origin.zip)
654
669
  end
655
670
 
656
- def destination_node(location)
671
+ def destination_node(xml, location)
657
672
  destination = sanitize_location(location)
658
673
  case destination.country_code
659
674
  when 'CA'
660
- XmlNode.new('destination') do |node|
661
- node << XmlNode.new('domestic') do |x|
662
- x << XmlNode.new('postal-code', destination.postal_code)
675
+ xml.public_send('destination') do
676
+ xml.public_send('domestic') do
677
+ xml.public_send('postal-code', destination.postal_code)
663
678
  end
664
679
  end
665
680
 
666
681
  when 'US'
667
- XmlNode.new('destination') do |node|
668
- node << XmlNode.new('united-states') do |x|
669
- x << XmlNode.new('zip-code', destination.postal_code)
682
+ xml.public_send('destination') do
683
+ xml.public_send('united-states') do
684
+ xml.public_send('zip-code', destination.postal_code)
670
685
  end
671
686
  end
672
687
 
673
688
  else
674
- XmlNode.new('destination') do |dest|
675
- dest << XmlNode.new('international') do |dom|
676
- dom << XmlNode.new('country-code', destination.country_code)
689
+ xml.public_send('destination') do
690
+ xml.public_send('international') do
691
+ xml.public_send('country-code', destination.country_code)
677
692
  end
678
693
  end
679
694
  end
680
695
  end
681
696
 
682
- def services_node(services)
683
- XmlNode.new('services') do |node|
684
- services.each { |code| node << XmlNode.new('service-code', code) }
697
+ def services_node(xml, services)
698
+ xml.public_send('services') do
699
+ services.each { |code| xml.public_send('service-code', code) }
685
700
  end
686
701
  end
687
702
 
688
- def shipping_options_node(available_options, options = {})
703
+ def shipping_options_node(xml, available_options, options = {})
689
704
  return if (options.symbolize_keys.keys & available_options).empty?
690
- XmlNode.new('options') do |el|
705
+ xml.public_send('options') do
691
706
 
692
707
  if options[:cod] && options[:cod_amount]
693
- el << XmlNode.new('option') do |opt|
694
- opt << XmlNode.new('option-code', 'COD')
695
- opt << XmlNode.new('option-amount', options[:cod_amount])
696
- opt << XmlNode.new('option-qualifier-1', options[:cod_includes_shipping]) unless options[:cod_includes_shipping].blank?
697
- opt << XmlNode.new('option-qualifier-2', options[:cod_method_of_payment]) unless options[:cod_method_of_payment].blank?
708
+ xml.public_send('option') do
709
+ xml.public_send('option-code', 'COD')
710
+ xml.public_send('option-amount', options[:cod_amount])
711
+ xml.public_send('option-qualifier-1', options[:cod_includes_shipping]) unless options[:cod_includes_shipping].blank?
712
+ xml.public_send('option-qualifier-2', options[:cod_method_of_payment]) unless options[:cod_method_of_payment].blank?
698
713
  end
699
714
  end
700
715
 
701
716
  if options[:cov]
702
- el << XmlNode.new('option') do |opt|
703
- opt << XmlNode.new('option-code', 'COV')
704
- opt << XmlNode.new('option-amount', options[:cov_amount]) unless options[:cov_amount].blank?
717
+ xml.public_send('option') do
718
+ xml.public_send('option-code', 'COV')
719
+ xml.public_send('option-amount', options[:cov_amount]) unless options[:cov_amount].blank?
705
720
  end
706
721
  end
707
722
 
708
723
  if options[:d2po]
709
- el << XmlNode.new('option') do |opt|
710
- opt << XmlNode.new('option-code', 'D2PO')
711
- opt << XmlNode.new('option-qualifier-2'. options[:d2po_office_id]) unless options[:d2po_office_id].blank?
724
+ xml.public_send('option') do
725
+ xml.public_send('option-code', 'D2PO')
726
+ xml.public_send('option-qualifier-2'. options[:d2po_office_id]) unless options[:d2po_office_id].blank?
712
727
  end
713
728
  end
714
729
 
715
730
  [:so, :dc, :pa18, :pa19, :hfp, :dns, :lad, :rase, :rts, :aban].each do |code|
716
731
  if options[code]
717
- el << XmlNode.new('option') do |opt|
718
- opt << XmlNode.new('option-code', code.to_s.upcase)
732
+ xml.public_send('option') do
733
+ xml.public_send('option-code', code.to_s.upcase)
719
734
  end
720
735
  end
721
736
  end
@@ -723,8 +738,8 @@ module ActiveShipping
723
738
  end
724
739
 
725
740
  def expected_date_from_node(node)
726
- if service = node.elements['service-standard']
727
- expected_date = service.get_text("expected-delivery-date").to_s
741
+ if service = node.at('service-standard/expected-delivery-date')
742
+ expected_date = service.text
728
743
  else
729
744
  expected_date = nil
730
745
  end
@@ -756,22 +771,22 @@ module ActiveShipping
756
771
  value == 0 ? 0.01 : value.round / 100.0
757
772
  end
758
773
 
759
- def origin_hash_for(root_node)
760
- occurrences = root_node.get_elements('significant-events').first.get_elements('occurrence')
774
+ def origin_hash_for(root)
775
+ occurrences = root.xpath('significant-events/occurrence')
761
776
  earliest = occurrences.sort_by { |occurrence| time_of_occurrence(occurrence) }.first
762
777
 
763
778
  {
764
- city: earliest.get_text('event-site').to_s,
765
- province: earliest.get_text('event-province').to_s,
766
- address_1: earliest.get_text('event-retail-location-id').to_s,
779
+ city: earliest.at('event-site').text,
780
+ province: earliest.at('event-province').text,
781
+ address_1: earliest.at('event-retail-location-id').text,
767
782
  country: 'Canada'
768
783
  }
769
784
  end
770
785
 
771
786
  def time_of_occurrence(occurrence)
772
- time = occurrence.get_text('event-time')
773
- date = occurrence.get_text('event-date')
774
- time_zone = occurrence.get_text('event-date')
787
+ time = occurrence.at('event-time').text
788
+ date = occurrence.at('event-date').text
789
+ time_zone = occurrence.at('event-time-zone').text
775
790
  DateTime.parse "#{date} #{time} #{time_zone}"
776
791
  end
777
792
  end