Class: Occi::Core::Parsers::TextParser

Inherits:
BaseParser
  • Object
show all
Defined in:
lib/occi/core/parsers/text_parser.rb

Overview

Implementes components necessary to parse all required instance types from `text` or `text`-like format.

Author:

Constant Summary collapse

URI_LIST_TYPES =

Media type constants

%w[text/uri-list].freeze
HEADERS_TEXT_TYPES =
%w[text/occi].freeze
PLAIN_TEXT_TYPES =
%w[text/plain text/occi+plain].freeze
OCCI_TEXT_TYPES =
[HEADERS_TEXT_TYPES, PLAIN_TEXT_TYPES].flatten.freeze
MEDIA_TYPES =
[URI_LIST_TYPES, OCCI_TEXT_TYPES].flatten.freeze
KEY_NAME_GROUPS =

Constants for OCCI keys

%w[LOCATION_KEYS CATEGORY_KEYS LINK_KEYS ATTRIBUTE_KEYS].freeze
LOCATION_KEYS =
%w[X-OCCI-Location X_occi_location X-occi-location Location].freeze
CATEGORY_KEYS =
%w[Category X-OCCI-Category X_occi_category X-occi-category].freeze
%w[Link X-OCCI-Link X_occi_link X-occi-link].freeze
ATTRIBUTE_KEYS =
%w[X-OCCI-Attribute X_occi_attribute X-occi-attribute].freeze
OCCI_KEYS =
[LOCATION_KEYS, CATEGORY_KEYS, LINK_KEYS, ATTRIBUTE_KEYS].flatten.freeze
HEADER_HTTP_PREFIX =

Constants for header normalization

'HTTP_'.freeze

Constants inherited from BaseParser

BaseParser::DELEGATED

Instance Attribute Summary

Attributes inherited from BaseParser

#media_type, #model

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseParser

#actions, #initialize, #kinds, #links, #lookup, media_types, #mixins, #parses?, parses?, #resources

Methods included from Helpers::ErrorHandler

#handle, included

Constructor Details

This class inherits a constructor from Occi::Core::Parsers::BaseParser

Class Method Details

.canonize_header_value(value) ⇒ Object

Attempts to canonize value from headers by looking for hidden multi-value lines usually separated by commas.

Parameters:

  • value (Object)

    value to canonize

Returns:

  • (Object)

    original or canonized value


222
223
224
225
226
# File 'lib/occi/core/parsers/text_parser.rb', line 222

def canonize_header_value(value)
  value = value.first if value.is_a?(Array) && value.count == 1 # ary with 1 could be hidden multi-val
  value = value.split(',').map(&:strip) if value.is_a?(String)  # multi-vals can hide as ','-separated String
  value
end

.canonize_headers(headers) ⇒ Hash

Canonizes normalized headers by eliminating naming differences in keys. Input for this method must be already normalized by `normalize_headers`.

Parameters:

  • headers (Hash)

    hash with normalized header key-value pairs

Returns:

  • (Hash)

    canonized hash with predictable keys


204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/occi/core/parsers/text_parser.rb', line 204

def canonize_headers(headers)
  validate_header_keys! headers.keys

  canonical = {}
  headers.each_pair do |key, value|
    pref = key_name_groups.detect { |ka| ka.include?(key) }
    canonical[pref.first] = [canonize_header_value(value)].flatten
  end

  logger.debug "Canonized headers #{canonical.inspect}" if logger_debug?
  canonical
end

.key_name_groupsArray

Returns a list of available key name groups accessible as constants by name on this class.

Returns:

  • (Array)

    list of available key name groups


258
259
260
# File 'lib/occi/core/parsers/text_parser.rb', line 258

def key_name_groups
  KEY_NAME_GROUPS.map { |kn| const_get kn }
end

.locations(body, headers, media_type) ⇒ Array

Extracts URI-like locations from body and headers. For details, see `Occi::Core::Parsers::Text::Location`.

Parameters:

  • body (String)

    raw `String`-like body as provided by the transport protocol

  • headers (Hash)

    raw headers as provided by the transport protocol

  • media_type (String)

    media type string as provided by the transport protocol

Returns:

  • (Array)

    list of extracted URIs


141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/occi/core/parsers/text_parser.rb', line 141

def locations(body, headers, media_type)
  if logger_debug?
    logger.debug "Parsing locations from #{media_type.inspect} in #{body.inspect} and #{headers.inspect}"
  end

  if URI_LIST_TYPES.include? media_type
    Text::Location.uri_list transform_body(body)
  elsif HEADERS_TEXT_TYPES.include? media_type
    Text::Location.plain transform_headers(headers)
  else
    Text::Location.plain transform_body(body)
  end
end

.model(body, headers, media_type, model) ⇒ Occi::Core::Model

Extracts categories from body and headers. For details, see `Occi::Core::Parsers::Text::Category`.

Parameters:

  • body (String)

    raw `String`-like body as provided by the transport protocol

  • headers (Hash)

    raw headers as provided by the transport protocol

  • media_type (String)

    media type string as provided by the transport protocol

  • model (Occi::Core::Model)

    `Model`-like instance to be populated (may contain existing categories)

Returns:


121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/occi/core/parsers/text_parser.rb', line 121

def model(body, headers, media_type, model)
  if logger_debug?
    logger.debug "Parsing model from #{media_type.inspect} in #{body.inspect} and #{headers.inspect}"
  end

  if HEADERS_TEXT_TYPES.include? media_type
    Text::Category.plain transform_headers(headers), model
  elsif PLAIN_TEXT_TYPES.include? media_type
    Text::Category.plain transform_body(body), model
  else
    raise Occi::Core::Errors::ParsingError, "Model cannot be parsed from #{media_type.inspect}"
  end
end

.normalize_headers(headers) ⇒ Hash

Normalizes raw headers into a more agreeable form. This process will remove known prefixes as well as blank lines or non-OCCI keys.

Parameters:

  • headers (Hash)

    hash with raw header key-value pairs

Returns:

  • (Hash)

    a cleaner hash with relevant headers


186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/occi/core/parsers/text_parser.rb', line 186

def normalize_headers(headers)
  return {} unless headers

  headers = Hash[
    headers.map { |k, v| [k.gsub(HEADER_HTTP_PREFIX, '').capitalize, v] }
  ]                                                 # remove 'HTTP_' prefix in keys
  headers.delete_if { |_k, v| v.blank? }            # remove pairs with empty values
  headers.keep_if { |k, _v| OCCI_KEYS.include?(k) } # drop non-OCCI pairs

  logger.debug "Normalized headers #{headers.inspect}" if logger_debug?
  headers
end

.transform_body(body) ⇒ Array

Transforms a `String`-like body into a series of independent lines. Note: Multi-line elements are not supported by this parser.

Parameters:

  • body (String)

    multi-line body

Returns:

  • (Array)

    an array of lines


160
161
162
163
164
# File 'lib/occi/core/parsers/text_parser.rb', line 160

def transform_body(body)
  return [] if body.blank?
  lines = body.respond_to?(:lines) ? body.lines : body.split("\n")
  lines.map(&:strip)
end

.transform_headers(headers) ⇒ Array

Transforms a widely varied content of headers into a unified body-like format for further processing. This method and its helpers should hide differences between `text/occi` and `text/plain` completely.

Parameters:

  • headers (Hash)

    hash with raw header key-value pairs

Returns:

  • (Array)

    an array of body-like lines


172
173
174
175
176
177
178
179
# File 'lib/occi/core/parsers/text_parser.rb', line 172

def transform_headers(headers)
  logger.debug "Transforming headers #{headers.inspect}" if logger_debug?
  unify_headers(
    canonize_headers(
      normalize_headers(headers)
    )
  )
end

.unify_headers(headers) ⇒ Array

Unifies canonized headers by transforming them into a body-like form. Output of this method should be directly parsable as `text/plain` found in body.

Parameters:

  • headers (Hash)

    hash with canonized header key-value pairs

Returns:

  • (Array)

    an array of body-like lines


234
235
236
237
238
239
240
241
242
# File 'lib/occi/core/parsers/text_parser.rb', line 234

def unify_headers(headers)
  lines = []
  headers.each_pair do |k, v|
    lines << v.map { |val| "#{k}: #{val}" }
  end

  logger.debug "Unified headers #{lines.inspect}" if logger_debug?
  lines.flatten.sort
end

.validate_header_keys!(headers_keys) ⇒ Object

Checks for use of multiple keys from a single key name groups. See `key_name_groups` for a list of available groups. Mixed key notations will produce an error.

Parameters:

  • headers_keys (Array)

    list of keys to check

Raises:


249
250
251
252
253
# File 'lib/occi/core/parsers/text_parser.rb', line 249

def validate_header_keys!(headers_keys)
  return unless key_name_groups.any? { |elm| (headers_keys & elm).count > 1 }

  raise Occi::Core::Errors::ParsingError, "Headers #{headers_keys.inspect} contain mixed key notations"
end

Instance Method Details

#action_instances(body, headers) ⇒ Set

Parses action instances from the given body/headers. Only actions already declared in the model are allowed.

Parameters:

  • body (String)

    raw `String`-like body as provided by the transport protocol

  • headers (Hash)

    raw headers as provided by the transport protocol

Returns:

  • (Set)

    set of parsed instances


70
71
72
73
74
75
76
77
78
79
80
# File 'lib/occi/core/parsers/text_parser.rb', line 70

def action_instances(body, headers)
  logger.debug "Parsing Occi::Core::ActionInstance from #{body.inspect} and #{headers.inspect}" if logger_debug?
  entity_parser = Text::Entity.new(model: model)
  tformed = transform(body, headers)

  action_instance = Occi::Core::ActionInstance.new(
    action: action_instance_category(entity_parser, tformed)
  )
  entity_parser.plain_attributes! tformed, action_instance.attributes
  setify(action_instance)
end

#categories(body, headers, expectation = nil) ⇒ Set

Parses categories from the given body/headers and returns corresponding instances from the known model.

Parameters:

  • body (String)

    raw `String`-like body as provided by the transport protocol

  • headers (Hash)

    raw headers as provided by the transport protocol

  • expectation (Class) (defaults to: nil)

    expected class of the returned instance(s)

Returns:

  • (Set)

    set of instances


89
90
91
92
93
94
95
96
97
98
99
# File 'lib/occi/core/parsers/text_parser.rb', line 89

def categories(body, headers, expectation = nil)
  expectation ||= Occi::Core::Category
  logger.debug "Parsing #{expectation} from #{body.inspect} and #{headers.inspect}" if logger_debug?

  cats = transform(body, headers).map do |line|
    cat = Text::Category.plain_category(line, false)
    lookup "#{cat[:scheme]}#{cat[:term]}", expectation
  end

  setify(cats)
end

#entities(body, headers, expectation = nil) ⇒ Set

Parses entities from the given body/headers. Only kinds, mixins, and actions already declared in the model are allowed.

Parameters:

  • body (String)

    raw `String`-like body as provided by the transport protocol

  • headers (Hash)

    raw headers as provided by the transport protocol

  • expectation (Class) (defaults to: nil)

    expected class of the returned instance(s)

Returns:

  • (Set)

    set of instances


51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/occi/core/parsers/text_parser.rb', line 51

def entities(body, headers, expectation = nil)
  expectation ||= Occi::Core::Entity
  logger.debug "Parsing #{expectation} from #{body.inspect} and #{headers.inspect}" if logger_debug?

  entity_parser = Text::Entity.new(model: model)
  entity = entity_parser.plain transform(body, headers)
  unless entity.is_a?(expectation)
    raise Occi::Core::Errors::ParsingError, "Entity is not of type #{expectation}"
  end

  setify(entity)
end