# typed: true # frozen_string_literal: true module Homebrew module Livecheck module Strategy # The {Xml} strategy fetches content at a URL, parses it as XML using # `REXML` and provides the `REXML::Document` to a `strategy` block. # If a regex is present in the `livecheck` block, it should be passed # as the second argument to the `strategy` block. # # This is a generic strategy that doesn't contain any logic for finding # versions, as the structure of XML data varies. Instead, a `strategy` # block must be used to extract version information from the XML data. # For more information on how to work with an `REXML::Document` object, # please refer to the [`REXML::Document`](https://ruby.github.io/rexml/REXML/Document.html) # and [`REXML::Element`](https://ruby.github.io/rexml/REXML/Element.html) # documentation. # # This strategy is not applied automatically and it is necessary to use # `strategy :xml` in a `livecheck` block (in conjunction with a # `strategy` block) to use it. # # This strategy's {find_versions} method can be used in other strategies # that work with XML content, so it should only be necessary to write # the version-finding logic that works with the parsed XML data. # # @api public class Xml NICE_NAME = "XML" # A priority of zero causes livecheck to skip the strategy. We do this # for {Xml} so we can selectively apply it only when a strategy block # is provided in a `livecheck` block. PRIORITY = 0 # The `Regexp` used to determine if the strategy applies to the URL. URL_MATCH_REGEX = %r{^https?://}i # Whether the strategy can be applied to the provided URL. # {Xml} will technically match any HTTP URL but is only usable with # a `livecheck` block containing a `strategy` block. # # @param url [String] the URL to match against # @return [Boolean] sig { params(url: String).returns(T::Boolean) } def self.match?(url) URL_MATCH_REGEX.match?(url) end # Parses XML text and returns an `REXML::Document` object. # @param content [String] the XML text to parse # @return [REXML::Document, nil] sig { params(content: String).returns(T.nilable(REXML::Document)) } def self.parse_xml(content) parsing_tries = 0 begin REXML::Document.new(content) rescue REXML::UndefinedNamespaceException => e undefined_prefix = e.to_s[/Undefined prefix ([^ ]+) found/i, 1] raise "Could not identify undefined prefix." if undefined_prefix.blank? # Only retry parsing once after removing prefix from content parsing_tries += 1 raise "Could not parse XML after removing undefined prefix." if parsing_tries > 1 # When an XML document contains a prefix without a corresponding # namespace, it's necessary to remove the prefix from the content # to be able to successfully parse it using REXML content = content.gsub(%r{(