CSS-Inliner
view release on metacpan or search on metacpan
lib/CSS/Inliner.pm view on Meta::CPAN
=cut
sub inlinify {
my ($self,$params) = @_;
$self->_check_object();
$self->_content_warnings({}); # overwrite any existing warnings
unless ($self->_html() && $self->_html_tree()) {
croak 'You must instantiate and read in your content before inlinifying';
}
# perform a check to see how bad this html is...
$self->_validate_html({ html => $self->_html() });
my $html;
if (defined $self->_css()) {
#parse and store the stylesheet as a hash object
$self->_css->read({ css => $self->_stylesheet() });
my @css_warnings = @{$self->_css->content_warnings()};
foreach my $css_warning (@css_warnings) {
$self->_report_warning({ info => $css_warning });
}
my %matched_elements;
my $count = 0;
foreach my $entry (@{$self->_css->get_rules()}) {
next unless exists $$entry{selector} && $$entry{declarations};
my $selector = $$entry{selector};
my $declarations = $$entry{declarations};
#skip over the following pseudo selectors, these particular ones are not inlineable
if ($selector =~ /(?:^|[\w\._\*\]])::?(?:([\w\-]+))\b/io && $1 !~ /first-child|last-child/i) {
$self->_report_warning({ info => "The pseudo-class ':$1' cannot be supported inline" });
next;
}
#skip over @import or anything else that might start with @ - not inlineable
if ($selector =~ /^\@/io) {
$self->_report_warning({ info => "The directive '$selector' cannot be supported inline" });
next;
}
my $query_result;
#check to see if query fails, possible for jacked selectors
eval {
$query_result = $self->query({ selector => $selector });
};
if ($@) {
$self->_report_warning({ info => $@->info() });
next;
}
# CSS rules cascade based on the specificity and order
my $specificity = $self->specificity({ selector => $selector });
#if an element matched a style within the document store the rule, the specificity
#and the actually CSS attributes so we can inline it later
foreach my $element (@{$query_result->get_elements()}) {
$matched_elements{$element->address()} ||= [];
my %match_info = (
rule => $selector,
element => $element,
specificity => $specificity,
position => $count,
css => $declarations
);
push(@{$matched_elements{$element->address()}}, \%match_info);
$count++;
}
}
#process all elements
foreach my $matches (values %matched_elements) {
my $element = $matches->[0]->{element};
# rules are sorted by specificity, and if there's a tie the position is used
# we sort with the lightest items first so that heavier items can override later
my @sorted_matches = sort { $a->{specificity} <=> $b->{specificity} || $a->{position} <=> $b->{position} } @$matches;
my @new_style;
my @new_important_style;
foreach my $match (@sorted_matches) {
push @new_style, @{$match->{css}};
push @new_important_style, _grep_important_declarations($match->{css});
}
# styles already inlined have greater precedence
if (defined($element->attr('style'))) {
my $cur_style = $self->_split(
{
style => $self->_encode_entities
? decode_entities($element->attr('style'))
: $element->attr('style')
}
);
push @new_style, @$cur_style;
push @new_important_style, _grep_important_declarations($cur_style);
}
# override styles with !important styles
push @new_style, @new_important_style;
my $new_style = $self->_expand({ declarations => \@new_style });
$element->attr('style', $self->_encode_entities ? encode_entities($new_style) : $new_style);
}
#at this point we have a document that contains the expanded inlined stylesheet
#BUT we need to collapse the declarations to remove duplicate overridden styles
$self->_collapse_inline_styles();
# dump out the final processed html for returning to the caller
( run in 1.538 second using v1.01-cache-2.11-cpan-e93a5daba3e )