Subject: | [PATCH] Added support for conditional comments containing style |
Patch is to add preliminary support (& documentation) for conditional
comments around style sheets. With the patch, the -style option accepts
a -cond argument. For example,
print start_html(-style => {
-media => 'all',
-cond => ['.foo {color: navy;}', '/style/1_IE.css',
[5, '/style/IE5.css'],
{-src => ['/style/2_IE.css', '/style/3_IE.css'],
-code => '.bar {padding: 1em;}'},
['/style/4.css'],
['!', '/style/notIE.css'],
{-expr => '((>=7)|(! 5)&IE',
-src => '/style/complex_IE.css',
-code => '.foo, .bar { border: #800;}'}
]
}
);
produces
<!--[if IE]>
<style type="text/css">
.foo {color: navy;}
</style>
<link rel="stylesheet" type="text/css" href="/style/1_IE.css" media="all">
<link rel="stylesheet" type="text/css" href="/style/2_IE.css" media="all">
<link rel="stylesheet" type="text/css" href="/style/3_IE.css" media="all">
<style type="text/css">
<!--
.bar {padding: 1em;}
-->
</style>
<![endif]-->
<!--[if IE 5]>
<link rel="stylesheet" type="text/css" href="/style/IE5.css" media="all">
<![endif]-->
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="/style/4.css" media="all">
<![endif]-->
<![if ! IE]>
<link rel="stylesheet" type="text/css" href="/style/notIE.css" >
<![endif]>
<!--[if ((gte IE 7)|(! IE 5)&IE]>
<link rel="stylesheet" type="text/css" href="/style/complex_IE.css"
media="all">
<style type="text/css">
<!--
.foo, .bar { border: #800;}
-->
</style>
<![endif]-->
Coming eventually: conditional comments for scripts and arbitrary header
elements.
Subject: | CGI.pm.patch |
--- CGI.pm.orig 2008-07-29 11:00:05.000000000 -0400
+++ CGI.pm 2008-08-01 03:49:51.000000000 -0400
@@ -1701,23 +1701,22 @@
####
'_style' => <<'END_OF_FUNC',
sub _style {
- my ($self,$style) = @_;
+ my ($self,$style,$cdata_start,$cdata_end) = @_;
my (@result);
my $type = 'text/css';
my $rel = 'stylesheet';
-
- my $cdata_start = $XHTML ? "\n<!--/* <![CDATA[ */" : "\n<!-- ";
- my $cdata_end = $XHTML ? "\n/* ]]> */-->\n" : " -->\n";
+ $cdata_start ||= $XHTML ? "\n<!--/* <![CDATA[ */" : "\n<!-- ";
+ $cdata_end ||= $XHTML ? "\n/* ]]> */-->\n" : " -->\n";
my @s = ref($style) eq 'ARRAY' ? @$style : $style;
my $other = '';
for my $s (@s) {
if (ref($s)) {
- my($src,$code,$verbatim,$stype,$alternate,$foo,@other) =
- rearrange([qw(SRC CODE VERBATIM TYPE ALTERNATE FOO)],
+ my($src,$code,$verbatim,$stype,$alternate,$cond,$foo,@other) =
+ rearrange([qw(SRC CODE VERBATIM TYPE ALTERNATE COND FOO)],
('-foo'=>'bar',
ref($s) eq 'ARRAY' ? @$s : %$s));
my $type = defined $stype ? $stype : 'text/css';
@@ -1729,15 +1728,16 @@
foreach $src (@$src)
{
push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
- : qq(<link rel="$rel" type="$type" href="$src"$other>)) if $src;
+ : qq(<link rel="$rel" type="$type" href="$src" $other>)) if $src;
}
}
else
{ # Otherwise, push the single -src, if it exists.
push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
- : qq(<link rel="$rel" type="$type" href="$src"$other>)
+ : qq(<link rel="$rel" type="$type" href="$src" $other>)
) if $src;
}
+ push(@result,$self->_condStyle($cond, $type, $rel, $other, @other)) if $cond;
if ($verbatim) {
my @v = ref($verbatim) eq 'ARRAY' ? @$verbatim : $verbatim;
push(@result, "<style type=\"text/css\">\n$_\n</style>") foreach @v;
@@ -1748,13 +1748,140 @@
} else {
my $src = $s;
push(@result,$XHTML ? qq(<link rel="$rel" type="$type" href="$src" $other/>)
- : qq(<link rel="$rel" type="$type" href="$src"$other>));
+ : qq(<link rel="$rel" type="$type" href="$src" $other>));
}
}
@result;
}
END_OF_FUNC
+### Method: _condStyle
+# internal method for generating conditional sections containing CSS style elements
+# Responsible for unpacking arrays containing mixed
+####
+'_condStyle' => <<'END_OF_FUNC',
+sub _condStyle {
+ my ($self,$cond, $type, $rel, $other, @other) = @_;
+
+ if (ref($cond) eq 'ARRAY') {
+ my (@result, @sheets);
+ if ($cond->[0] =~ /^!?[glteq<>=]*\s*(?:ie\s*)?\d+(?:\.\d+)?$/i) {
+ push @sheets, shift @$cond;
+ }
+ foreach $c (@$cond) {
+ if (ref $c) {
+ if (ref $c eq 'HASH' && !defined($c->{'-expr'})) {
+ push(@sheets, $c);
+ } else {
+ push(@result,$self->_condSheets($c, $type, $rel, $other, @other)) if $c;
+ }
+ } else {
+ push(@sheets, $c);
+ }
+ }
+ # unshift to put the general IE style before the more specific style
+ unshift(@result,$self->_condSheets(\@sheets, $type, $rel, $other, @other)) if @sheets;
+ return @result;
+ } else {
+ return $self->_condSheets($cond, $type, $rel, $other, @other);
+ }
+}
+END_OF_FUNC
+
+### Method: _sheet
+# internal method for generating an external or header style, based on content
+# content can be: a scalar
+###
+'_sheet' => <<'END_OF_FUNC',
+sub _sheet ($$;$$$) {
+ my ($self,$content, $type, $rel, $other) = @_;
+ if ($content =~ m%/\*.*\*/|{(?:\s*[-_a-z]*:.*;?)*\s*}%) {
+ # can't use CDATA
+ # $cdata_(start|end) are locally defined elsewhere
+ return style({'type'=>$type},"$cdata_start\n$content\n$cdata_end");
+ } else {
+ return $XHTML ? qq(<link rel="$rel" type="$type" href="$content" $other/>)
+ : qq(<link rel="$rel" type="$type" href="$content" $other>)
+ }
+}
+END_OF_FUNC
+
+### Method: _condSheets
+# internal method for generating a conditional section containing CSS style sections
+# _condSheets($CGI, $cond, $type, $rel, $other, @other)
+# If $cond is an array ref, all elements must be scalars or hashes with no
+# '-expr' key; the 1st element may be an expression. if $cond is a hash, it is
+# passed through _style; it should not contain any '-cond' sections
+####
+'_condSheets' => <<'END_OF_FUNC',
+sub _condSheets {
+ my ($self,$cond, $type, $rel, $other, @other) = @_;
+ my $expr = 'IE';
+
+ # can't use HTML comments within a conditional comment, so need different $cdata_.*
+ local $cdata_start = $XHTML ? "\n/* <![CDATA[ */" : '';
+ local $cdata_end = $XHTML ? "\n/* ]]> */\n" : '';
+
+ if (ref $cond) {
+ if (ref $cond eq 'HASH' && $cond->{'-expr'}) {
+ $expr = delete $cond->{'-expr'};
+ } elsif (ref $cond eq 'ARRAY'
+ && $cond->[0] =~ /^(?:[ l<g>n]?t?[>e=]?\s*(?:ie\s*)?(?:\d(?:\.\d+)?)|[ !()|&]+|false|true)+$/i)
+ {
+ # above RE is more permissive than the expression grammer, but still
+ # won't match URIs or style sheets (no false positives). Here's a more
+ # restrictive RE that has false negatives:
+ # /^(?:\s*!?\s*\(?[ glt<>=!]*[=e]?\s*(?:ie\s*)?(?:\d+(?:\.\d+)?|true|false|[&|])\)?\s*[&|]?)+$/i
+ # eg '(7|(9|5.5))'. Of course, the likelihood of these more complex
+ # expressions is low.
+ $expr = shift @$cond;
+ }
+ $expr =~ s/eq|==//gi; # strip 'eq' and '=='
+ $expr =~ s/(?:!=|ne|<>)\s*/!/gi; # replace '!=', 'ne' and '<>' with '!'
+ $expr =~ s/([gl])e/$1te/g; # replace 'le' and 'ge' with 'lte' and 'gte'
+ $expr =~ s/</lt/g; #
+ $expr =~ s/>/gt/g; #
+ $expr =~ s/=\s*/e /g; #
+ # insert "IE" where it's missing
+ $expr =~ s/(^|[gl!]t?e?)\s*(\d)|(!)\s*(?:\)|$)/$1$3 IE $2/gi;
+ $expr =~ s/^\s+|\s+$//g;
+ }
+ my $uplevel = '--';
+ # if expression is '! IE', then the conditional comment is targeting non-IE
+ # browsers, so we want a "downlevel revealed" conditional comment (ie not a comment)
+ if ($expr =~ /^!\s*IE$/) {
+ $uplevel = '';
+ }
+
+ my @result = ("<!${uplevel}[if $expr]>");
+ if (ref $cond eq 'ARRAY') {
+ foreach my $sheet (@$cond) {
+ if (ref($sheet) eq 'HASH') {
+ $sheet->{'-type'} ||= $type;
+ $sheet->{'-alternate'} = 1 if $rel ne 'stylesheet';
+ my %other = @other;
+ @{$sheet}{keys %other} = values %other;
+ # sanity check for $sheet->{'-cond'}?
+ push @result, $self->_style($sheet, $cdata_start, $cdata_end);
+ } else {
+ push @result, $self->_sheet($sheet, $type, $rel, $other);
+ }
+ }
+ } elsif (ref $cond eq 'HASH') {
+ $cond->{'-type'} ||= $type;
+ $cond->{'-alternate'} = 1 if $rel ne 'stylesheet';
+ my %other = @other;
+ @{$cond}{keys %other} = values %other;
+ push @result, $self->_style($cond, $cdata_start, $cdata_end);
+ } else {
+ push @result, $self->_sheet($cond, $type, $rel, $other);
+ }
+ push @result, "<![endif]$uplevel>";
+ return @result;
+}
+END_OF_FUNC
+
+
'_script' => <<'END_OF_FUNC',
sub _script {
my ($self,$script) = @_;
@@ -7133,14 +7260,83 @@
start_html() method a B<-style> parameter. The value of this
parameter may be a scalar, in which case it is treated as the source
URL for the stylesheet, or it may be a hash reference. In the latter
-case you should provide the hash with one or more of B<-src> or
-B<-code>. B<-src> points to a URL where an externally-defined
+case you should provide the hash with one or more of B<-src>, B<-code>
+or B<-cond>. B<-src> points to a URL where an externally-defined
stylesheet can be found. B<-code> points to a scalar value to be
incorporated into a <style> section. Style definitions in B<-code>
override similarly-named ones in B<-src>, hence the name "cascading."
-You may also specify the type of the stylesheet by adding the optional
-B<-type> parameter to the hash pointed to by B<-style>. If not
+B<-cond> will wrap the style sheet(s) in a conditional comment. The
+basic values of a B<-cond> can be a string, an array or a hash.
+A string is a URI path or CSS rules (CGI.pm will automatically
+pick which). A hash is similar to what you might pass to a B<-style>
+argument, but it cannot contain any B<-cond> and can optionally contain
+a B<-expr> argument, which becomes the conditional expression
+("<!--[if $expr]>"). An array starts with an optional expression; if
+absent, it defaults to the expression 'IE'.
+
+You can also pass B<-cond> an array of basic values; this array can also
+start with an optional conditional expression, which becomes the expression
+for all scalars and hashes missing and '-expr' key, but not for arrays (see
+complex example below).
+
+Conditional expressions are slightly expanded from what IE accepts: you
+can use symbolic comparisons (eg '<' for 'lt'; '<>7', '!=7' or 'ne 7'
+for '! IE 7') and don't need to include the 'IE' feature (eg 'gte 6' for
+'gte IE 6'). The expanded form will be translated to one IE handles.
+
+A B<-cond> can be as simple as:
+ -cond => '/style/site_IE.css'
+which produces:
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="/style/site_IE.css" />
+ <![endif]-->
+and can be as complex as:
+ -cond => [5.5, '.foo {color: navy;}', '/style/1_IE.css',
+ [5, '/style/IE5.css'],
+ {-src => ['/style/2_IE.css', '/style/3_IE.css'],
+ -code => '.bar {padding: 1em;}'},
+ ['/style/4.css'],
+ ['!', '/style/notIE.css'],
+ {-expr => '((>=7)|(! 5)&IE',
+ -src => '/style/complex_IE.css',
+ -code => '.foo, .bar { border: #800;}'}
+ ]
+which produces:
+ <!--[if IE 5.5]>
+ <style type="text/css">
+ .foo {color: navy;}
+ </style>
+ <link rel="stylesheet" type="text/css" href="/style/1_IE.css" >
+ <link rel="stylesheet" type="text/css" href="/style/2_IE.css" >
+ <link rel="stylesheet" type="text/css" href="/style/3_IE.css" >
+ <style type="text/css">
+ <!--
+ .bar {padding: 1em;}
+ -->
+ </style>
+ <![endif]-->
+ <!--[if IE 5]>
+ <link rel="stylesheet" type="text/css" href="/style/IE5.css" >
+ <![endif]-->
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="/style/4.css" >
+ <![endif]-->
+ <![if ! IE]>
+ <link rel="stylesheet" type="text/css" href="/style/notIE.css" >
+ <![endif]>
+ <!--[if ((gte IE 7)|(! IE 5)&IE]>
+ <link rel="stylesheet" type="text/css" href="/style/complex_IE.css" >
+ <style type="text/css">
+ <!--
+ .foo, .bar { border: #800;}
+ -->
+ </style>
+ <![endif]-->
+
+
+You may also specify the type of the stylesheet(s) by adding the optional
+B<-type> parameter to the hash pointed to by B<-style>. If not
specified, the style defaults to 'text/css'.
To refer to a style within the body of your document, add the
@@ -7219,13 +7415,16 @@
incorporated into the <link> tag. For example:
start_html(-style=>{-src=>['/styles/print.css','/styles/layout.css'],
+ -cond=>'/styles/IE.css',
-media => 'all'});
This will give:
<link rel="stylesheet" type="text/css" href="/styles/print.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/styles/layout.css" media="all"/>
-
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="/styles/IE.css" media="all"/>
+ <![endif]-->
<p>
To make more complicated <link> tags, use the Link() function