I am echoing Dave's warning. Since it's not a full HTML document, and you can assume the structure is never changing, here is a similar solution to Dave's that will work with more kinds of words than just adjectives and verbs.
use strict;
use warnings;
use feature "say";
{
local $/ = qq{</>\n};
while (my $record = <DATA>) {
# each record is one set of word, <h2> and <span>, separated by newline
my ($word, $h2, $span) = split /\n/, $record;
# remove span tag and break into words
(my $option_csv) = $span =~ m/>(.+)</;
my @options = split /,\s*/, $option_csv;
# remove tag from phrase
(my $phrase) = $h2 =~ m/>(.+)</;
# replace the word in the phrase with a placeholder, keep whitespace and
# extra words like 'to'
$phrase =~ s{
( # capture into $1
[^+]* # zero or more chars that are not a plus
$word # the word we're looking to replace
[^+]* # zero or more chars that are not a plus
)
}{@}x; # replace with a single @ as the placeholder
my $word_with_whitespace = $1;
# build all the phrases from the options by replacing everything
# but the placeholder with the option
my $all_phrases = join ', ', map { $phrase =~ s/[^@]+/$_/r } @options;
# put the word back into all of them
$all_phrases =~ s/@/$word_with_whitespace/g;
# put the assembled list of phrases back into the span tag
$span =~ s{
>
(.+)
<
}{>$all_phrases<}x;
say $word;
say $h2;
say $span;
say q{</>};
}
}
__DATA__
cat
<h2>adjective + cat</h2>
<span>cute, pretty, small</span>
</>
choose
<h2>choose to + verb</h2>
<span>go, play, study</span>
</>
The idea is the same, but it's flexible on where the word goes in the h2. I've used the /x modifier on substitutions to explain what my patterns are doing. There's also a /r modifier, that returns the result of the substitution rather than change the original variable.
We can make this work for multiple sets of <h2> and <span> by using an array when splitting up the record. We can then treat them as pairs, loop over these and put them back together.
I've converted it to a function and removed the </> delimiter so I can use Test::More on it.
use strict;
use warnings;
use Test::More;
my ($input, $expected);
$input = <<EOT;
cat
<h2>adjective + cat</h2>
<span>cute, pretty, small</span>
EOT
$expected = <<EOT;
cat
<h2>adjective + cat</h2>
<span>cute cat, pretty cat, small cat</span>
</>
EOT
is foo($input), $expected, 'one set of adjectives';
$input = <<EOT;
choose
<h2>choose to + verb</h2>
<span>go, play, study</span>
EOT
$expected = <<EOT;
choose
<h2>choose to + verb</h2>
<span>choose to go, choose to play, choose to study</span>
</>
EOT
is foo($input), $expected, 'one set of verbs';
$input = <<EOT;
cat
<h2>adjective + cat</h2>
<span>round, fluffy</span>
<h2>adjective + cat</h2>
<span>small, soft, little ball of fur</span>
EOT
$expected = <<EOT;
cat
<h2>adjective + cat</h2>
<span>round cat, fluffy cat</span>
<h2>adjective + cat</h2>
<span>small cat, soft cat, little ball of fur cat</span>
</>
EOT
is foo($input), $expected, 'two sets of adjectives';
$input = <<EOT;
decide
<h2>decide to + verb</h2>
<span>answer</span>
<h2>decide to + verb</h2>
<span>help, figure out</span>
<h2>decide to + verb</h2>
<span>arrive, stay, wait, leave</span>
EOT
$expected = <<EOT;
decide
<h2>decide to + verb</h2>
<span>decide to answer</span>
<h2>decide to + verb</h2>
<span>decide to help, decide to figure out</span>
<h2>decide to + verb</h2>
<span>decide to arrive, decide to stay, decide to wait, decide to leave</span>
</>
EOT
is foo($input), $expected, 'three sets of verbs';
done_testing;
sub foo {
my $record = shift;
# each record is one set of word, <h2> and <span>, separated by newline
my ($word, @rest) = split /\n/, $record;
die 'wrong number of arguments' if scalar @rest % 2;
my @output = $word;
while ( @rest ) {
my $h2 = shift @rest;
my $span = shift @rest;
# remove span tag and break into words
(my $option_csv) = $span =~ m/>(.+)</;
my @options = split /,\s*/, $option_csv;
# remove tag from phrase
(my $phrase) = $h2 =~ m/>(.+)</;
# replace the word in the phrase with a placeholder, keep whitespace and
# extra words like 'to'
$phrase =~ s{
( # capture into $1
[^+]* # zero or more chars that are not a plus
$word # the word we're looking to replace
[^+]* # zero or more chars that are not a plus
)
}{@}x; # replace with a single @ as the placeholder
my $word_with_whitespace = $1;
# build all the phrases from the options by replacing everything
# but the placeholder with the option
my $all_phrases = join ', ', map { $phrase =~ s/[^@]+/$_/r } @options;
# put the word back into all of them
$all_phrases =~ s/@/$word_with_whitespace/g;
# put the assembled list of phrases back into the span tag
$span =~ s{
>
(.+)
<
}{>$all_phrases<}x;
push @output, $h2, $span;
}
return join "\n", @output, qq{</>\n};
}