# frozen_string_literal: true module Html2rss ## # Provides a namespace for item extractors. module ItemExtractors ## # The Error class to be thrown when an unknown extractor name is requested. class UnknownExtractorName < Html2rss::Error; end ## # Maps the extractor name to the class implementing the extractor. # # The key is the name to use in the feed config. NAME_TO_CLASS = { attribute: Attribute, href: Href, html: Html, static: Static, text: Text }.freeze ## # Maps the extractor class to its corresponding options class. ITEM_OPTION_CLASSES = Hash.new do |hash, klass| hash[klass] = klass.const_get(:Options) end DEFAULT_EXTRACTOR = :text ## # Retrieves an element from Nokogiri XML based on the selector. # # @param xml [Nokogiri::XML::Document] # @param selector [String, nil] # @return [Nokogiri::XML::ElementSet] selected XML elements def self.element(xml, selector) selector ? xml.css(selector) : xml end ## # Creates an instance of the requested item extractor. # # @param attribute_options [Hash] # Should contain at least `:extractor` (the name) and required options for that extractor. # @param xml [Nokogiri::XML::Document] # @return [Object] instance of the specified item extractor class def self.item_extractor_factory(attribute_options, xml) extractor_name = attribute_options[:extractor]&.to_sym || DEFAULT_EXTRACTOR extractor_class = find_extractor_class(extractor_name) options_instance = build_options_instance(extractor_class, attribute_options) create_extractor_instance(extractor_class, xml, options_instance) end ## # Finds the extractor class based on the name. # # @param extractor_name [Symbol] the name of the extractor # @return [Class] the class implementing the extractor # @raise [UnknownExtractorName] if the extractor class is not found def self.find_extractor_class(extractor_name) NAME_TO_CLASS[extractor_name] || raise(UnknownExtractorName, "Unknown extractor name '#{extractor_name}' requested in NAME_TO_CLASS") end ## # Builds the options instance for the extractor class. # # @param extractor_class [Class] the class implementing the extractor # @param attribute_options [Hash] the attribute options # @return [Object] an instance of the options class for the extractor def self.build_options_instance(extractor_class, attribute_options) options = attribute_options.slice(*extractor_class::Options.members) ITEM_OPTION_CLASSES[extractor_class].new(options) end ## # Creates an instance of the extractor class. # # @param extractor_class [Class] the class implementing the extractor # @param xml [Nokogiri::XML::Document] the XML document # @param options_instance [Object] the options instance # @return [Object] an instance of the extractor class def self.create_extractor_instance(extractor_class, xml, options_instance) extractor_class.new(xml, options_instance) end end end