lib/active_shipping/carriers/fedex.rb in active_shipping-1.0.0.pre4 vs lib/active_shipping/carriers/fedex.rb in active_shipping-1.0.0
- old
+ new
@@ -1,14 +1,11 @@
-# FedEx module by Jimmy Baker
-# http://github.com/jimmyebaker
-
-require 'date'
module ActiveShipping
- # :key is your developer API key
- # :password is your API password
- # :account is your FedEx account number
- # :login is your meter number
+
+ # FedEx carrier implementation.
+ #
+ # FedEx module by Jimmy Baker (http://github.com/jimmyebaker)
+ # Documentation can be found here: http://images.fedex.com/us/developer/product/WebServices/MyWebHelp/PropDevGuide.pdf
class FedEx < Carrier
self.retry_safe = true
cattr_reader :name
@@name = "FedEx"
@@ -85,11 +82,12 @@
'ground_shipment_id' => 'GROUND_SHIPMENT_ID',
'ground_invoice_number' => 'GROUND_INVOICE_NUMBER',
'ground_customer_reference' => 'GROUND_CUSTOMER_REFERENCE',
'ground_po' => 'GROUND_PO',
'express_reference' => 'EXPRESS_REFERENCE',
- 'express_mps_master' => 'EXPRESS_MPS_MASTER'
+ 'express_mps_master' => 'EXPRESS_MPS_MASTER',
+ 'shipper_reference' => 'SHIPPER_REFERENCE',
}
TRANSIT_TIMES = %w(UNKNOWN ONE_DAY TWO_DAYS THREE_DAYS FOUR_DAYS FIVE_DAYS SIX_DAYS SEVEN_DAYS EIGHT_DAYS NINE_DAYS TEN_DAYS ELEVEN_DAYS TWELVE_DAYS THIRTEEN_DAYS FOURTEEN_DAYS FIFTEEN_DAYS SIXTEEN_DAYS SEVENTEEN_DAYS EIGHTEEN_DAYS)
# FedEx tracking codes as described in the FedEx Tracking Service WSDL Guide
@@ -141,241 +139,230 @@
packages = Array(packages)
rate_request = build_rate_request(origin, destination, packages, options)
xml = commit(save_request(rate_request), (options[:test] || false))
- response = remove_version_prefix(xml)
- parse_rate_response(origin, destination, packages, response, options)
+ parse_rate_response(origin, destination, packages, xml, options)
end
def find_tracking_info(tracking_number, options = {})
options = @options.update(options)
tracking_request = build_tracking_request(tracking_number, options)
xml = commit(save_request(tracking_request), (options[:test] || false))
- response = remove_version_prefix(xml)
- parse_tracking_response(response, options)
+ parse_tracking_response(xml, options)
end
protected
def build_rate_request(origin, destination, packages, options = {})
imperial = %w(US LR MM).include?(origin.country_code(:alpha2))
- xml_request = XmlNode.new('RateRequest', 'xmlns' => 'http://fedex.com/ws/rate/v13') do |root_node|
- root_node << build_request_header
- root_node << build_version_node
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
+ xml.RateRequest(xmlns: 'http://fedex.com/ws/rate/v13') do
+ build_request_header(xml)
+ build_version_node(xml, 'crs', 13, 0 ,0)
- # Returns delivery dates
- root_node << XmlNode.new('ReturnTransitAndCommit', true)
- # Returns saturday delivery shipping options when available
- root_node << XmlNode.new('VariableOptions', 'SATURDAY_DELIVERY')
+ # Returns delivery dates
+ xml.ReturnTransitAndCommit(true)
- root_node << XmlNode.new('RequestedShipment') do |rs|
- rs << XmlNode.new('ShipTimestamp', ship_timestamp(options[:turn_around_time]))
+ # Returns saturday delivery shipping options when available
+ xml.VariableOptions('SATURDAY_DELIVERY')
- freight = has_freight?(options)
+ xml.RequestedShipment do
+ xml.ShipTimestamp(ship_timestamp(options[:turn_around_time]).iso8601(0))
- unless freight
- # fedex api wants this up here otherwise request returns an error
- rs << XmlNode.new('DropoffType', options[:dropoff_type] || 'REGULAR_PICKUP')
- rs << XmlNode.new('PackagingType', options[:packaging_type] || 'YOUR_PACKAGING')
- end
+ freight = has_freight?(options)
- rs << build_location_node('Shipper', (options[:shipper] || origin))
- rs << build_location_node('Recipient', destination)
- if options[:shipper] and options[:shipper] != origin
- rs << build_location_node('Origin', origin)
- end
+ unless freight
+ # fedex api wants this up here otherwise request returns an error
+ xml.DropoffType(options[:dropoff_type] || 'REGULAR_PICKUP')
+ xml.PackagingType(options[:packaging_type] || 'YOUR_PACKAGING')
+ end
- if freight
- # build xml for freight rate requests
- freight_options = options[:freight]
- rs << build_shipping_charges_payment_node(freight_options)
- rs << build_freight_shipment_detail_node(freight_options, packages, imperial)
- rs << build_rate_request_types_node
- else
- # build xml for non-freight rate requests
- rs << XmlNode.new('SmartPostDetail') do |spd|
- spd << XmlNode.new('Indicia', options[:smart_post_indicia] || 'PARCEL_SELECT')
- spd << XmlNode.new('HubId', options[:smart_post_hub_id] || 5902) # default to LA
+ build_location_node(xml, 'Shipper', options[:shipper] || origin)
+ build_location_node(xml, 'Recipient', destination)
+ if options[:shipper] && options[:shipper] != origin
+ build_location_node(xml, 'Origin', origin)
end
- rs << build_rate_request_types_node
- rs << XmlNode.new('PackageCount', packages.size)
- rs << build_packages_nodes(packages, imperial)
+ if freight
+ freight_options = options[:freight]
+ build_shipping_charges_payment_node(xml, freight_options)
+ build_freight_shipment_detail_node(xml, freight_options, packages, imperial)
+ build_rate_request_types_node(xml)
+ else
+ xml.SmartPostDetail do
+ xml.Indicia(options[:smart_post_indicia] || 'PARCEL_SELECT')
+ xml.HubId(options[:smart_post_hub_id] || 5902) # default to LA
+ end
+ build_rate_request_types_node(xml)
+ xml.PackageCount(packages.size)
+ build_packages_nodes(xml, packages, imperial)
+ end
end
end
end
- xml_request.to_s
+ xml_builder.to_xml
end
- def build_packages_nodes(packages, imperial)
+ def build_packages_nodes(xml, packages, imperial)
packages.map do |pkg|
- XmlNode.new('RequestedPackageLineItems') do |rps|
- rps << XmlNode.new('GroupPackageCount', 1)
- rps << build_package_weight_node(pkg, imperial)
- rps << build_package_dimensions_node(pkg, imperial)
+ xml.RequestedPackageLineItems do
+ xml.GroupPackageCount(1)
+ build_package_weight_node(xml, pkg, imperial)
+ build_package_dimensions_node(xml, pkg, imperial)
end
end
end
- def build_shipping_charges_payment_node(freight_options)
- XmlNode.new('ShippingChargesPayment') do |shipping_charges_payment|
- shipping_charges_payment << XmlNode.new('PaymentType', freight_options[:payment_type])
- shipping_charges_payment << XmlNode.new('Payor') do |payor|
- payor << XmlNode.new('ResponsibleParty') do |responsible_party|
+ def build_shipping_charges_payment_node(xml, freight_options)
+ xml.ShippingChargesPayment do
+ xml.PaymentType(freight_options[:payment_type])
+ xml.Payor do
+ xml.ResponsibleParty do
# TODO: case of different freight account numbers?
- responsible_party << XmlNode.new('AccountNumber', freight_options[:account])
+ xml.AccountNumber(freight_options[:account])
end
end
end
end
- def build_freight_shipment_detail_node(freight_options, packages, imperial)
- XmlNode.new('FreightShipmentDetail') do |freight_shipment_detail|
+ def build_freight_shipment_detail_node(xml, freight_options, packages, imperial)
+ xml.FreightShipmentDetail do
# TODO: case of different freight account numbers?
- freight_shipment_detail << XmlNode.new('FedExFreightAccountNumber', freight_options[:account])
- freight_shipment_detail << build_location_node('FedExFreightBillingContactAndAddress', freight_options[:billing_location])
- freight_shipment_detail << XmlNode.new('Role', freight_options[:role])
+ xml.FedExFreightAccountNumber(freight_options[:account])
+ build_location_node(xml, 'FedExFreightBillingContactAndAddress', freight_options[:billing_location])
+ xml.Role(freight_options[:role])
packages.each do |pkg|
- freight_shipment_detail << XmlNode.new('LineItems') do |line_items|
- line_items << XmlNode.new('FreightClass', freight_options[:freight_class])
- line_items << XmlNode.new('Packaging', freight_options[:packaging])
- line_items << build_package_weight_node(pkg, imperial)
- line_items << build_package_dimensions_node(pkg, imperial)
+ xml.LineItems do
+ xml.FreightClass(freight_options[:freight_class])
+ xml.Packaging(freight_options[:packaging])
+ build_package_weight_node(xml, pkg, imperial)
+ build_package_dimensions_node(xml, pkg, imperial)
end
end
end
end
def has_freight?(options)
options[:freight] && options[:freight].present?
end
- def build_package_weight_node(pkg, imperial)
- XmlNode.new('Weight') do |tw|
- tw << XmlNode.new('Units', imperial ? 'LB' : 'KG')
- tw << XmlNode.new('Value', [((imperial ? pkg.lbs : pkg.kgs).to_f * 1000).round / 1000.0, 0.1].max)
+ def build_package_weight_node(xml, pkg, imperial)
+ xml.Weight do
+ xml.Units(imperial ? 'LB' : 'KG')
+ xml.Value([((imperial ? pkg.lbs : pkg.kgs).to_f * 1000).round / 1000.0, 0.1].max)
end
end
- def build_version_node
- XmlNode.new('Version') do |version_node|
- version_node << XmlNode.new('ServiceId', 'crs')
- version_node << XmlNode.new('Major', '13')
- version_node << XmlNode.new('Intermediate', '0')
- version_node << XmlNode.new('Minor', '0')
- end
- end
-
- def build_package_dimensions_node(pkg, imperial)
- XmlNode.new('Dimensions') do |dimensions|
+ def build_package_dimensions_node(xml, pkg, imperial)
+ xml.Dimensions do
[:length, :width, :height].each do |axis|
value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f * 1000).round / 1000.0 # 3 decimals
- dimensions << XmlNode.new(axis.to_s.capitalize, value.ceil)
+ xml.public_send(axis.to_s.capitalize, value.ceil)
end
- dimensions << XmlNode.new('Units', imperial ? 'IN' : 'CM')
+ xml.Units(imperial ? 'IN' : 'CM')
end
end
- def build_rate_request_types_node(type = 'ACCOUNT')
- XmlNode.new('RateRequestTypes', type)
+ def build_rate_request_types_node(xml, type = 'ACCOUNT')
+ xml.RateRequestTypes(type)
end
def build_tracking_request(tracking_number, options = {})
- xml_request = XmlNode.new('TrackRequest', 'xmlns' => 'http://fedex.com/ws/track/v3') do |root_node|
- root_node << build_request_header
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
+ xml.TrackRequest(xmlns: 'http://fedex.com/ws/track/v7') do
+ build_request_header(xml)
+ build_version_node(xml, 'trck', 7, 0, 0)
- # Version
- root_node << XmlNode.new('Version') do |version_node|
- version_node << XmlNode.new('ServiceId', 'trck')
- version_node << XmlNode.new('Major', '3')
- version_node << XmlNode.new('Intermediate', '0')
- version_node << XmlNode.new('Minor', '0')
- end
+ xml.SelectionDetails do
+ xml.PackageIdentifier do
+ xml.Type(PACKAGE_IDENTIFIER_TYPES[options[:package_identifier_type] || 'tracking_number'])
+ xml.Value(tracking_number)
+ end
- root_node << XmlNode.new('PackageIdentifier') do |package_node|
- package_node << XmlNode.new('Value', tracking_number)
- package_node << XmlNode.new('Type', PACKAGE_IDENTIFIER_TYPES[options['package_identifier_type'] || 'tracking_number'])
- end
+ xml.ShipDateRangeBegin(options[:ship_date_range_begin]) if options[:ship_date_range_begin]
+ xml.ShipDateRangeEnd(options[:ship_date_range_end]) if options[:ship_date_range_end]
+ xml.TrackingNumberUniqueIdentifier(options[:unique_identifier]) if options[:unique_identifier]
+ end
- root_node << XmlNode.new('ShipDateRangeBegin', options['ship_date_range_begin']) if options['ship_date_range_begin']
- root_node << XmlNode.new('ShipDateRangeEnd', options['ship_date_range_end']) if options['ship_date_range_end']
- root_node << XmlNode.new('IncludeDetailedScans', 1)
+ xml.ProcessingOptions('INCLUDE_DETAILED_SCANS')
+ end
end
- xml_request.to_s
+ xml_builder.to_xml
end
- def build_request_header
- web_authentication_detail = XmlNode.new('WebAuthenticationDetail') do |wad|
- wad << XmlNode.new('UserCredential') do |uc|
- uc << XmlNode.new('Key', @options[:key])
- uc << XmlNode.new('Password', @options[:password])
+ def build_request_header(xml)
+ xml.WebAuthenticationDetail do
+ xml.UserCredential do
+ xml.Key(@options[:key])
+ xml.Password(@options[:password])
end
end
- client_detail = XmlNode.new('ClientDetail') do |cd|
- cd << XmlNode.new('AccountNumber', @options[:account])
- cd << XmlNode.new('MeterNumber', @options[:login])
+ xml.ClientDetail do
+ xml.AccountNumber(@options[:account])
+ xml.MeterNumber(@options[:login])
end
- trasaction_detail = XmlNode.new('TransactionDetail') do |td|
- td << XmlNode.new('CustomerTransactionId', @options[:transaction_id] || 'ActiveShipping') # TODO: Need to do something better with this..
+ xml.TransactionDetail do
+ xml.CustomerTransactionId(@options[:transaction_id] || 'ActiveShipping') # TODO: Need to do something better with this...
end
+ end
- [web_authentication_detail, client_detail, trasaction_detail]
+ def build_version_node(xml, service_id, major, intermediate, minor)
+ xml.Version do
+ xml.ServiceId(service_id)
+ xml.Major(major)
+ xml.Intermediate(intermediate)
+ xml.Minor(minor)
+ end
end
- def build_location_node(name, location)
- XmlNode.new(name) do |xml_node|
- xml_node << XmlNode.new('Address') do |address_node|
- address_node << XmlNode.new('StreetLines', location.address1) if location.address1
- address_node << XmlNode.new('StreetLines', location.address2) if location.address2
- address_node << XmlNode.new('City', location.city) if location.city
- address_node << XmlNode.new('PostalCode', location.postal_code)
- address_node << XmlNode.new("CountryCode", location.country_code(:alpha2))
-
- address_node << XmlNode.new("Residential", true) unless location.commercial?
+ def build_location_node(xml, name, location)
+ xml.public_send(name) do
+ xml.Address do
+ xml.StreetLines(location.address1) if location.address1
+ xml.StreetLines(location.address2) if location.address2
+ xml.City(location.city) if location.city
+ xml.PostalCode(location.postal_code)
+ xml.CountryCode(location.country_code(:alpha2))
+ xml.Residential(true) unless location.commercial?
end
end
end
def parse_rate_response(origin, destination, packages, response, options)
- rate_estimates = []
+ xml = build_document(response, 'RateReply')
- xml = build_document(response)
- root_node = xml.elements['RateReply']
-
success = response_success?(xml)
message = response_message(xml)
- raise ActiveShipping::ResponseContentError.new(StandardError.new('Invalid document'), xml) unless root_node
-
- root_node.elements.each('RateReplyDetails') do |rated_shipment|
- service_code = rated_shipment.get_text('ServiceType').to_s
- is_saturday_delivery = rated_shipment.get_text('AppliedOptions').to_s == 'SATURDAY_DELIVERY'
+ rate_estimates = xml.root.css('> RateReplyDetails').map do |rated_shipment|
+ service_code = rated_shipment.at('ServiceType').text
+ is_saturday_delivery = rated_shipment.at('AppliedOptions').try(:text) == 'SATURDAY_DELIVERY'
service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
- transit_time = rated_shipment.get_text('TransitTime').to_s if service_code == "FEDEX_GROUND"
- max_transit_time = rated_shipment.get_text('MaximumTransitTime').to_s if service_code == "FEDEX_GROUND"
+ transit_time = rated_shipment.at('TransitTime').text if service_code == "FEDEX_GROUND"
+ max_transit_time = rated_shipment.at('MaximumTransitTime').try(:text) if service_code == "FEDEX_GROUND"
- delivery_timestamp = rated_shipment.get_text('DeliveryTimestamp').to_s
+ delivery_timestamp = rated_shipment.at('DeliveryTimestamp').try(:text)
delivery_range = delivery_range_from(transit_time, max_transit_time, delivery_timestamp, options)
- currency = rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').to_s
- rate_estimates << RateEstimate.new(origin, destination, @@name,
- self.class.service_name_for_code(service_type),
- :service_code => service_code,
- :total_price => rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').to_s.to_f,
- :currency => currency,
- :packages => packages,
- :delivery_range => delivery_range)
+ currency = rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').text
+ RateEstimate.new(origin, destination, @@name,
+ self.class.service_name_for_code(service_type),
+ :service_code => service_code,
+ :total_price => rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').text.to_f,
+ :currency => currency,
+ :packages => packages,
+ :delivery_range => delivery_range)
end
if rate_estimates.empty?
success = false
message = "No shipping rates could be found for the destination address" if message.blank?
@@ -411,61 +398,84 @@
def business_day?(date)
(1..5).include?(date.wday)
end
def parse_tracking_response(response, options)
- xml = build_document(response)
- root_node = xml.elements['TrackReply']
+ xml = build_document(response, 'TrackReply')
success = response_success?(xml)
message = response_message(xml)
if success
origin = nil
delivery_signature = nil
shipment_events = []
- tracking_details = root_node.elements['TrackDetails']
- tracking_number = tracking_details.get_text('TrackingNumber').to_s
- status_code = tracking_details.get_text('StatusCode').to_s
- status_description = tracking_details.get_text('StatusDescription').to_s
- status = TRACKING_STATUS_CODES[status_code]
+ all_tracking_details = xml.root.xpath('CompletedTrackDetails/TrackDetails')
+ tracking_details = case all_tracking_details.length
+ when 1
+ all_tracking_details.first
+ when 0
+ raise ActiveShipping::Error, "The response did not contain tracking details"
+ else
+ all_unique_identifiers = xml.root.xpath('CompletedTrackDetails/TrackDetails/TrackingNumberUniqueIdentifier').map(&:text)
+ raise ActiveShipping::Error, "Multiple matches were found. Specify a unqiue identifier: #{all_unique_identifiers.join(', ')}"
+ end
- if status_code == 'DL' && tracking_details.get_text('SignatureProofOfDeliveryAvailable').to_s == 'true'
- delivery_signature = tracking_details.get_text('DeliverySignatureName').to_s
+
+ first_notification = tracking_details.at('Notification')
+ if first_notification.at('Severity').text == 'ERROR'
+ case first_notification.at('Code').text
+ when '9040'
+ raise ActiveShipping::ShipmentNotFound, first_notification.at('Message').text
+ else
+ raise ActiveShipping::ResponseContentError, first_notification.at('Message').text
+ end
end
- origin_node = tracking_details.elements['OriginLocationAddress']
+ tracking_number = tracking_details.at('TrackingNumber').text
+ status_detail = tracking_details.at('StatusDetail')
+ if status_detail.nil?
+ raise ActiveShipping::Error, "Tracking response does not contain status information"
+ end
- if origin_node
+ status_code = status_detail.at('Code').text
+ status_description = (status_detail.at('AncillaryDetails/ReasonDescription') || status_detail.at('Description')).text
+ status = TRACKING_STATUS_CODES[status_code]
+
+ if status_code == 'DL' && tracking_details.at('AvailableImages').try(:text) == 'SIGNATURE_PROOF_OF_DELIVERY'
+ delivery_signature = tracking_details.at('DeliverySignatureName').text
+ end
+
+ if origin_node = tracking_details.at('OriginLocationAddress')
origin = Location.new(
- :country => origin_node.get_text('CountryCode').to_s,
- :province => origin_node.get_text('StateOrProvinceCode').to_s,
- :city => origin_node.get_text('City').to_s
+ :country => origin_node.at('CountryCode').text,
+ :province => origin_node.at('StateOrProvinceCode').text,
+ :city => origin_node.at('City').text
)
end
destination = extract_address(tracking_details, DELIVERY_ADDRESS_NODE_NAMES)
shipper_address = extract_address(tracking_details, SHIPPER_ADDRESS_NODE_NAMES)
ship_time = extract_timestamp(tracking_details, 'ShipTimestamp')
actual_delivery_time = extract_timestamp(tracking_details, 'ActualDeliveryTimestamp')
scheduled_delivery_time = extract_timestamp(tracking_details, 'EstimatedDeliveryTimestamp')
- tracking_details.elements.each('Events') do |event|
- address = event.elements['Address']
+ tracking_details.xpath('Events').each do |event|
+ address = event.at('Address')
+ next if address.nil? || address.at('CountryCode').nil?
- city = address.get_text('City').to_s
- state = address.get_text('StateOrProvinceCode').to_s
- zip_code = address.get_text('PostalCode').to_s
- country = address.get_text('CountryCode').to_s
- next if country.blank?
+ city = address.at('City').try(:text)
+ state = address.at('StateOrProvinceCode').try(:text)
+ zip_code = address.at('PostalCode').try(:text)
+ country = address.at('CountryCode').try(:text)
location = Location.new(:city => city, :state => state, :postal_code => zip_code, :country => country)
- description = event.get_text('EventDescription').to_s
+ description = event.at('EventDescription').text
- time = Time.parse("#{event.get_text('Timestamp').to_s}")
+ time = Time.parse(event.at('Timestamp').text)
zoneless_time = time.utc
shipment_events << ShipmentEvent.new(description, zoneless_time, location)
end
shipment_events = shipment_events.sort_by(&:time)
@@ -499,26 +509,21 @@
def ship_date(delay_in_hours)
delay_in_hours ||= 0
(Time.now + delay_in_hours.hours).to_date
end
- def response_status_node(document)
- document.elements['/*/Notifications/']
- end
-
def response_success?(document)
- response_node = response_status_node(document)
- return false if response_node.nil?
-
- %w(SUCCESS WARNING NOTE).include? response_node.get_text('Severity').to_s
+ highest_severity = document.root.at('HighestSeverity')
+ return false if highest_severity.nil?
+ %w(SUCCESS WARNING NOTE).include?(highest_severity.text)
end
def response_message(document)
- response_node = response_status_node(document)
- return "" if response_node.nil?
+ notifications = document.root.at('Notifications')
+ return "" if notifications.nil?
- "#{response_node.get_text('Severity')} - #{response_node.get_text('Code')}: #{response_node.get_text('Message')}"
+ "#{notifications.at('Severity').text} - #{notifications.at('Code').text}: #{notifications.at('Message').text}"
end
def commit(request, test = false)
ssl_post(test ? TEST_URL : LIVE_URL, request.gsub("\n", ''))
end
@@ -533,19 +538,19 @@
end
def extract_address(document, possible_node_names)
node = nil
possible_node_names.each do |name|
- node ||= document.elements[name]
+ node = document.at(name)
break if node
end
- args = if node && node.elements['CountryCode']
+ args = if node && node.at('CountryCode')
{
- :country => node.get_text('CountryCode').to_s,
- :province => node.get_text('StateOrProvinceCode').to_s,
- :city => node.get_text('City').to_s
+ :country => node.at('CountryCode').text,
+ :province => node.at('StateOrProvinceCode').text,
+ :city => node.at('City').text
}
else
{
:country => ActiveUtils::Country.new(:alpha2 => 'ZZ', :name => 'Unknown or Invalid Territory', :alpha3 => 'ZZZ', :numeric => '999'),
:province => 'unknown',
@@ -555,25 +560,26 @@
Location.new(args)
end
def extract_timestamp(document, node_name)
- if timestamp_node = document.elements[node_name]
- Time.parse(timestamp_node.to_s).utc
+ if timestamp_node = document.at(node_name)
+ if timestamp_node.text =~ /\A(\d{4}-\d{2}-\d{2})T00:00:00\Z/
+ Date.parse($1)
+ else
+ Time.parse(timestamp_node.text)
+ end
end
end
- def remove_version_prefix(xml)
- if xml =~ /xmlns:v[0-9]/
- xml.gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
- else
- xml
+ def build_document(xml, expected_root_tag)
+ document = Nokogiri.XML(xml) { |config| config.strict }
+ document.remove_namespaces!
+ if document.root.nil? || document.root.name != expected_root_tag
+ raise ActiveShipping::ResponseContentError.new(StandardError.new('Invalid document'), xml)
end
- end
-
- def build_document(xml)
- REXML::Document.new(xml)
- rescue REXML::ParseException => e
+ document
+ rescue Nokogiri::XML::SyntaxError => e
raise ActiveShipping::ResponseContentError.new(e, xml)
end
end
end