1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
module OpenGraphReader
# Convert a {Parser::Graph} into the right hierarchy of {Object}s attached
# to a {Base}, then validate it.
#
# @api private
class Builder
# Well-known types from
#
# @see http://ogp.me
KNOWN_TYPES = %w(website article book profile).freeze
# Create a new builder.
#
# @param [Parser] parser
# @see Parser#graph
# @see Parser#additional_namespaces
def initialize parser
@parser = parser
end
# Build and return the base.
#
# @return [Base]
def base
base = Base.new
type = @parser.graph.fetch("og:type", "website").downcase
validate_type type
@parser.graph.each do |property|
build_property base, property
end
synthesize_required_properties base
drop_empty_children base
validate base
base
end
private
def build_property base, property
object, name = object_and_name base, property
if collection? object, name
build_collection object, property, name
elsif subobject? property
build_subobject object, property, name
else # Direct attribute
build_single object, property, name
end
rescue UnknownNamespaceError, UndefinedPropertyError => e
raise InvalidObjectError, e.message if OpenGraphReader.config.strict
end
def object_and_name base, property
root, *path, name = property.path
base[root] ||= Object::Registry[root].new
object = resolve base[root], root, path
[object, name]
end
def collection? object, name
object.property?(name) && object.respond_to?("#{name}s")
end
def build_collection object, property, name
collection = object.public_send "#{name}s"
if Object::Registry.registered? property.fullname # of subobjects
object = Object::Registry[property.fullname].new
collection << object
object.content = property.content
else # of type
collection << property.content
end
end
def subobject? property
Object::Registry.registered? property.fullname
end
def build_subobject object, property, name
object[name] ||= Object::Registry[property.fullname].new
object[name].content = property.content
end
def build_single object, property, name
object[name] = property.content
end
def resolve object, last_namespace, path
return object if path.empty?
next_name = path.shift
if object.property?(next_name) && object.respond_to?("#{next_name}s") # collection
resolve_collection object, last_namespace, next_name
else
resolve_property object, last_namespace, next_name
end
end
def resolve_collection object, last_namespace, next_name
collection = object.public_send("#{next_name}s")
next_object = collection.last
# Final namespace or missing previous declaration, create a new collection item
if next_object.nil?
next_object = Object::Registry[[*last_namespace, next_name].join(":")].new
collection << next_object
end
next_object
end
def resolve_property object, last_namespace, next_name
next_object = object[next_name]
next_object || Object::Registry[[*last_namespace, next_name].join(":")].new
end
def synthesize_required_properties base
synthesize_url base
synthesize_title base
end
def synthesize_url base
return unless OpenGraphReader.config.synthesize_url
return if base.og.url
base.og["url"] = OpenGraphReader.current_origin
end
def synthesize_title base
return unless OpenGraphReader.config.synthesize_title
return if base.og.title
base.og["title"] = @parser.title
end
def drop_empty_children base
base = base.children
base.each do |key, object|
[*object].each do |object|
if object.is_a? Object
drop_empty_children object
base.delete(key) if object.content.nil? && object.children.empty? && object.properties.empty?
end
end
end
end
def validate_type type
return unless OpenGraphReader.config.strict
return if KNOWN_TYPES.include?(type)
return if @parser.additional_namespaces.include?(type)
return if Object::Registry.verticals.include?(type)
raise InvalidObjectError, "Undefined type #{type}"
end
def validate base
base.each do |object|
validate_required object if OpenGraphReader.config.validate_required
validate_verticals object, base.og.type
end
end
def validate_required object
object.class.required_properties.each do |property|
if object[property].nil?
raise InvalidObjectError, "Missing required property #{property} on #{object.inspect}"
end
end
end
def validate_verticals object, type
return unless type.include? "."
verticals = object.class.verticals
return unless verticals.has_key? type
return if extra_properties(object, type, verticals).empty?
raise InvalidObjectError, "Set invalid property #{extra_properties.first} for #{type} " \
"in #{object.inspect}, valid properties are #{valid_properties.inspect}"
end
def extra_properties object, type, verticals
valid_properties = verticals[type]
set_properties = object.class.available_properties.select {|property| object[property] }
set_properties - valid_properties
end
end
end
|