PageRenderTime 425ms CodeModel.GetById 181ms app.highlight 15ms RepoModel.GetById 227ms app.codeStats 0ms

/lib/httparty/parser.rb

http://github.com/jnunemaker/httparty
Ruby | 150 lines | 78 code | 18 blank | 54 comment | 9 complexity | e74fd24fcca1565accb183d539e51588 MD5 | raw file
  1module HTTParty
  2  # The default parser used by HTTParty, supports xml, json, html, csv and
  3  # plain text.
  4  #
  5  # == Custom Parsers
  6  #
  7  # If you'd like to do your own custom parsing, subclassing HTTParty::Parser
  8  # will make that process much easier. There are a few different ways you can
  9  # utilize HTTParty::Parser as a superclass.
 10  #
 11  # @example Intercept the parsing for all formats
 12  #   class SimpleParser < HTTParty::Parser
 13  #     def parse
 14  #       perform_parsing
 15  #     end
 16  #   end
 17  #
 18  # @example Add the atom format and parsing method to the default parser
 19  #   class AtomParsingIncluded < HTTParty::Parser
 20  #     SupportedFormats.merge!(
 21  #       {"application/atom+xml" => :atom}
 22  #     )
 23  #
 24  #     def atom
 25  #       perform_atom_parsing
 26  #     end
 27  #   end
 28  #
 29  # @example Only support the atom format
 30  #   class ParseOnlyAtom < HTTParty::Parser
 31  #     SupportedFormats = {"application/atom+xml" => :atom}
 32  #
 33  #     def atom
 34  #       perform_atom_parsing
 35  #     end
 36  #   end
 37  #
 38  # @abstract Read the Custom Parsers section for more information.
 39  class Parser
 40    SupportedFormats = {
 41      'text/xml'                    => :xml,
 42      'application/xml'             => :xml,
 43      'application/json'            => :json,
 44      'application/vnd.api+json'    => :json,
 45      'application/hal+json'        => :json,
 46      'text/json'                   => :json,
 47      'application/javascript'      => :plain,
 48      'text/javascript'             => :plain,
 49      'text/html'                   => :html,
 50      'text/plain'                  => :plain,
 51      'text/csv'                    => :csv,
 52      'application/csv'             => :csv,
 53      'text/comma-separated-values' => :csv
 54    }
 55
 56    # The response body of the request
 57    # @return [String]
 58    attr_reader :body
 59
 60    # The intended parsing format for the request
 61    # @return [Symbol] e.g. :json
 62    attr_reader :format
 63
 64    # Instantiate the parser and call {#parse}.
 65    # @param [String] body the response body
 66    # @param [Symbol] format the response format
 67    # @return parsed response
 68    def self.call(body, format)
 69      new(body, format).parse
 70    end
 71
 72    # @return [Hash] the SupportedFormats hash
 73    def self.formats
 74      const_get(:SupportedFormats)
 75    end
 76
 77    # @param [String] mimetype response MIME type
 78    # @return [Symbol]
 79    # @return [nil] mime type not supported
 80    def self.format_from_mimetype(mimetype)
 81      formats[formats.keys.detect {|k| mimetype.include?(k)}]
 82    end
 83
 84    # @return [Array<Symbol>] list of supported formats
 85    def self.supported_formats
 86      formats.values.uniq
 87    end
 88
 89    # @param [Symbol] format e.g. :json, :xml
 90    # @return [Boolean]
 91    def self.supports_format?(format)
 92      supported_formats.include?(format)
 93    end
 94
 95    def initialize(body, format)
 96      @body = body
 97      @format = format
 98    end
 99
100    # @return [Object] the parsed body
101    # @return [nil] when the response body is nil, an empty string, spaces only or "null"
102    def parse
103      return nil if body.nil?
104      return nil if body == "null"
105      return nil if body.valid_encoding? && body.strip.empty?
106      if body.valid_encoding? && body.encoding == Encoding::UTF_8
107        @body = body.gsub(/\A#{UTF8_BOM}/, '')
108      end
109      if supports_format?
110        parse_supported_format
111      else
112        body
113      end
114    end
115
116    protected
117
118    def xml
119      MultiXml.parse(body)
120    end
121
122    UTF8_BOM = "\xEF\xBB\xBF".freeze
123
124    def json
125      JSON.parse(body, :quirks_mode => true, :allow_nan => true)
126    end
127
128    def csv
129      CSV.parse(body)
130    end
131
132    def html
133      body
134    end
135
136    def plain
137      body
138    end
139
140    def supports_format?
141      self.class.supports_format?(format)
142    end
143
144    def parse_supported_format
145      send(format)
146    rescue NoMethodError => e
147      raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
148    end
149  end
150end