Xml: Add #element_text method

This refactors verbose code in the `Sparkle` strategy where we access
element text into a reusable `Xml#element_text` method, replacing
chained calls like `item.elements["title"]&.text&.strip&.presence`
with `Xml.element_text(item, "title")`.

`#element_text` is only used to retrieve the text of a child element
in the `Sparkle` strategy but it can also retrieve the text from the
provided element if the `child_path` argument is omitted (i.e.,
`Xml.element_text(item)`). This will allow us to also avoid similar
calls like `item.text.strip.presence` in the future.
This commit is contained in:
Sam Ford 2023-11-17 18:06:20 -05:00
parent 5fa5f3b0aa
commit 9bfe423a5a
No known key found for this signature in database
GPG Key ID: 7AF5CBEE1DD6F76D
3 changed files with 73 additions and 8 deletions

View File

@ -95,16 +95,16 @@ module Homebrew
os = enclosure["os"].presence
end
title = item.elements["title"]&.text&.strip&.presence
link = item.elements["link"]&.text&.strip&.presence
title = Xml.element_text(item, "title")
link = Xml.element_text(item, "link")
url ||= link
channel = item.elements["channel"]&.text&.strip&.presence
release_notes_link = item.elements["releaseNotesLink"]&.text&.strip&.presence
short_version ||= item.elements["shortVersionString"]&.text&.strip&.presence
version ||= item.elements["version"]&.text&.strip&.presence
channel = Xml.element_text(item, "channel")
release_notes_link = Xml.element_text(item, "releaseNotesLink")
short_version ||= Xml.element_text(item, "shortVersionString")
version ||= Xml.element_text(item, "version")
minimum_system_version_text =
item.elements["minimumSystemVersion"]&.text&.strip&.gsub(/\A\D+|\D+\z/, "")&.presence
Xml.element_text(item, "minimumSystemVersion")&.gsub(/\A\D+|\D+\z/, "")
if minimum_system_version_text.present?
minimum_system_version = begin
MacOSVersion.new(minimum_system_version_text)
@ -113,7 +113,7 @@ module Homebrew
end
end
pub_date = item.elements["pubDate"]&.text&.strip&.presence&.then do |date_string|
pub_date = Xml.element_text(item, "pubDate")&.then do |date_string|
Time.parse(date_string)
rescue ArgumentError
# Omit unparsable strings (e.g. non-English dates)

View File

@ -72,6 +72,30 @@ module Homebrew
end
end
# Retrieves the stripped inner text of an `REXML` element. Returns
# `nil` if the optional child element doesn't exist or the text is
# blank.
# @param element [REXML::Element] an `REXML` element to retrieve text
# from, either directly or from a child element
# @param child_path [String, nil] the XPath of a child element to
# retrieve text from
# @return [String, nil]
sig {
params(
element: REXML::Element,
child_path: T.nilable(String),
).returns(T.nilable(String))
}
def self.element_text(element, child_path = nil)
element = element.get_elements(child_path).first if child_path.present?
return if element.nil?
text = element.text
return if text.blank?
text.strip
end
# Parses XML text and identifies versions using a `strategy` block.
# If a regex is provided, it will be passed as the second argument to
# the `strategy` block (after the parsed XML data).

View File

@ -79,6 +79,24 @@ describe Homebrew::Livecheck::Strategy::Xml do
EOS
end
let(:parent_child_text) { { parent: "1.2.3", child: "4.5.6" } }
let(:content_parent_child) do
# This XML deliberately includes unnecessary whitespace, to ensure that
# Xml#element_text properly strips the retrieved text.
<<~EOS
<?xml version="1.0" encoding="utf-8"?>
<elements>
<parent>
#{parent_child_text[:parent]}
<child> #{parent_child_text[:child]} </child>
</parent>
<blank-parent>
<blank-child></blank-child>
</blank-parent>
</elements>
EOS
end
let(:content_matches) { ["1.1.2", "1.1.1", "1.1.0", "1.0.3", "1.0.2", "1.0.1", "1.0.0"] }
let(:content_simple_matches) { ["1.2.3"] }
@ -123,6 +141,29 @@ describe Homebrew::Livecheck::Strategy::Xml do
end
end
describe "::element_text" do
let(:parent_child_doc) { xml.parse_xml(content_parent_child) }
let(:parent) { parent_child_doc.get_elements("/elements/parent").first }
let(:blank_parent) { parent_child_doc.get_elements("/elements/blank-parent").first }
it "returns the element text if child_name is not provided" do
expect(xml.element_text(parent)).to eq(parent_child_text[:parent])
end
it "returns the child element text if child_name is provided" do
expect(xml.element_text(parent, "child")).to eq(parent_child_text[:child])
end
it "returns `nil` if the provided child element does not exist" do
expect(xml.element_text(parent, "nonexistent")).to be_nil
end
it "returns `nil` if the retrieved text is blank" do
expect(xml.element_text(blank_parent)).to be_nil
expect(xml.element_text(blank_parent, "blank-child")).to be_nil
end
end
describe "::versions_from_content" do
it "returns an empty array when given a block but content is blank" do
expect(xml.versions_from_content("", regex) { "1.2.3" }).to eq([])