require 'nokogiri' require 'mida' module Mida # Class that holds a validated item class Item # The vocabulary used to interpret this item attr_reader :vocabulary # The Type of the item attr_reader :type # The Global Identifier of the item attr_reader :id # A Hash representing the properties as name/values paris # The values will be an array containing either +String+ # or Mida::Item instances attr_reader :properties # Create a new Item object from an +Itemscope+ and validates # its +properties+ # # [itemscope] The itemscope that has been parsed by +Itemscope+ # [validate] Whether to validate the item against known vocabularies def initialize(itemscope, validate=true) @type = itemscope.type @id = itemscope.id @vocabulary = Mida::Vocabulary.find(@type) @properties = itemscope.properties validate_properties if validate end # Return a Hash representation # of the form: # { type: 'http://example.com/vocab/review', # id: 'urn:isbn:1-934356-08-5', # properties: {'a name' => 'avalue' } # } def to_h # Only fill hash with non-nil values hash = {} @type and hash[:type] = @type @id and hash[:id] = @id @properties.any? and hash[:properties] = properties_to_h(@properties) hash end def to_s to_h.to_s end def ==(other) @vocabulary == other.vocabulary && @type == other.type && @id == other.id && @properties == other.properties end private # Validate the properties so that they are in their proper form def validate_properties @properties = @properties.each_with_object({}) do |(property, values), hash| valid_values = validate_values(property, values) if valid_values.respond_to?(:any?) hash[property] = valid_values if valid_values.any? else hash[property] = valid_values end end end # Return whether the number of values conforms to +num+ def valid_num_values?(num, values) num == :many || (num == :one && values.length == 1) end # Return whether this property name is valid def valid_property?(property, values) [property, :any].any? do |prop| @vocabulary.properties.has_key?(prop) end end # Return valid values, converted to the correct +DataType+ # or +Item+ and number if necessary def validate_values(property, values) return [] unless valid_property?(property, values) prop_num = property_number(property) return [] unless valid_num_values?(prop_num, values) prop_types = property_types(property) valid_values = values.each_with_object([]) do |value, valid_values| new_value = validate_value(prop_types, value) valid_values << new_value unless new_value.nil? end # Convert property to correct number prop_num == :many ? valid_values : valid_values.first end # Returns value converted to correct +DataType+ or +Item+ # or +nil+ if not valid def validate_value(prop_types, value) if is_itemscope?(value) valid_itemtype?(prop_types, value.type) ? Item.new(value) : nil elsif (extract_value = datatype_extract(prop_types, value)) extract_value elsif prop_types.include?(:any) value else nil end end # Return the correct type for this property def property_types(property) if @vocabulary.properties.has_key?(property) @vocabulary.properties[property][:types] else @vocabulary.properties[:any][:types] end end # Return the correct number for this property def property_number(property) if @vocabulary.properties.has_key?(property) @vocabulary.properties[property][:num] else @vocabulary.properties[:any][:num] end end def is_itemscope?(object) object.kind_of?(Itemscope) end # Returns whether the +itemtype+ is a valid type def valid_itemtype?(valid_types, itemtype) return true if valid_types.include?(:any) valid_types.find do |type| type.respond_to?(:itemtype) && type.itemtype =~ itemtype end end # Returns the extracted value or +nil+ if none of the datatypes # could extract the +value+ def datatype_extract(valid_types, value) valid_types.find do |type| begin return type.extract(value) if type.respond_to?(:extract) rescue ArgumentError end end nil end # The value as it should appear in to_h() def value_to_h(value) if value.is_a?(Array) then value.collect {|element| value_to_h(element)} elsif value.is_a?(Item) then value.to_h else value end end def properties_to_h(properties) properties.each_with_object({}) do |(name, value), hash| hash[name] = value_to_h(value) end end end end