File: builder.rb

package info (click to toggle)
ruby-open-graph-reader 0.6.2%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,980 kB
  • ctags: 133
  • sloc: ruby: 1,505; xml: 22; makefile: 2
file content (196 lines) | stat: -rw-r--r-- 5,566 bytes parent folder | download | duplicates (4)
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