/lib/httparty/parser.rb

http://github.com/jnunemaker/httparty · Ruby · 150 lines · 78 code · 18 blank · 54 comment · 9 complexity · e74fd24fcca1565accb183d539e51588 MD5 · raw file

  1. module 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. # The response body of the request
  56. # @return [String]
  57. attr_reader :body
  58. # The intended parsing format for the request
  59. # @return [Symbol] e.g. :json
  60. attr_reader :format
  61. # Instantiate the parser and call {#parse}.
  62. # @param [String] body the response body
  63. # @param [Symbol] format the response format
  64. # @return parsed response
  65. def self.call(body, format)
  66. new(body, format).parse
  67. end
  68. # @return [Hash] the SupportedFormats hash
  69. def self.formats
  70. const_get(:SupportedFormats)
  71. end
  72. # @param [String] mimetype response MIME type
  73. # @return [Symbol]
  74. # @return [nil] mime type not supported
  75. def self.format_from_mimetype(mimetype)
  76. formats[formats.keys.detect {|k| mimetype.include?(k)}]
  77. end
  78. # @return [Array<Symbol>] list of supported formats
  79. def self.supported_formats
  80. formats.values.uniq
  81. end
  82. # @param [Symbol] format e.g. :json, :xml
  83. # @return [Boolean]
  84. def self.supports_format?(format)
  85. supported_formats.include?(format)
  86. end
  87. def initialize(body, format)
  88. @body = body
  89. @format = format
  90. end
  91. # @return [Object] the parsed body
  92. # @return [nil] when the response body is nil, an empty string, spaces only or "null"
  93. def parse
  94. return nil if body.nil?
  95. return nil if body == "null"
  96. return nil if body.valid_encoding? && body.strip.empty?
  97. if body.valid_encoding? && body.encoding == Encoding::UTF_8
  98. @body = body.gsub(/\A#{UTF8_BOM}/, '')
  99. end
  100. if supports_format?
  101. parse_supported_format
  102. else
  103. body
  104. end
  105. end
  106. protected
  107. def xml
  108. MultiXml.parse(body)
  109. end
  110. UTF8_BOM = "\xEF\xBB\xBF".freeze
  111. def json
  112. JSON.parse(body, :quirks_mode => true, :allow_nan => true)
  113. end
  114. def csv
  115. CSV.parse(body)
  116. end
  117. def html
  118. body
  119. end
  120. def plain
  121. body
  122. end
  123. def supports_format?
  124. self.class.supports_format?(format)
  125. end
  126. def parse_supported_format
  127. send(format)
  128. rescue NoMethodError => e
  129. raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
  130. end
  131. end
  132. end