Je l'ai programmé en PHP5. Il se présente sous forme de class.
Les fonctions requises
function Parser_STR_char_is_escaped( $string, $position, $start = 0 )
{
$length = strlen( $string );
if ( ( $position === $start ) || ( $length <= $position ) )
return false;
$position--;
$escaped = false;
while ( ( $position >= $start ) && ( $string{ $position } === '\\' ) )
{
$position--;
$escaped = !$escaped;
}
return $escaped;
}
function Parser_STR_is_space( $c )
{
return ereg( "[ \t\n]", $c );
}
function Parser_STR_str_in_str( $needle, $haystack )
{
if ( strpos( $haystack, $needle ) === false )
return false;
else
return true;
}
La classe
class CSS_Parser
{
private $bracket_list = '()[]{}';
private $punct_list = ',>;:*=+';
private $quote_list = '\'"';
private $css_combined_punct = array
(
'|=' => 1, '~=' => 1
);
private $css_function_name = array
(
'attr' => 1, 'counter' => 1, 'lang' => 1, 'url' => 1, 'wave' => 1
);
private $css = '';
private $len = 0;
private $css_array = array();
private $size = 0;
public function __construct
(
$sql // string Requête SQL
)
{
$this->parse( $sql );
$this->format_html();
}
public function display()
{
echo $this->css;
}
private function identifier( $c )
{
return
(
ereg( '[-A-Za-z0-9_%/\#\.\!]', $c )
);
}
private function parse( $css )
{
$this->css = $css;
// Convertit tous les retours ligne au type Unix
$this->css = str_replace( "\r\n", "\n", $this->css );
$this->css = str_replace( "\r", "\n", $this->css );
$this->len = strlen( $this->css );
if ( strlen( $css ) === 0 )
return array();
$count1 = 0;
$count2 = 0;
$inbloc = false;
while ( $count2 < $this->len )
{
$c = $this->css{ $count2 };
$count1 = $count2;
if ( Parser_STR_is_space( $c ) )
{
$count2++;
continue;
}
// Recherche les commentaires
if ( ( $c === '/' ) && ( $count2 + 1 < $this->len ) && ( $this->css{ $count2 + 1 } === '*' ) )
{
$count2++;
$pos = strpos( $this->css, "*/", $count2 );
if ( $pos === false )
die( 'PARSER_CSS_ERROR::UNCLOSED_COMMENT@' . $count1 . "\n" . 'STR: ' . substr( $this->css, $count1 ) );
$pos += 2;
if ( $pos < $count2 )
$count2 = $this->len;
else
$count2 = $pos;
$this->array_add( 'comment', substr( $this->css, $count1, $count2 - $count1 ) );
continue;
}
// Recherche les chaines entre quotes
if ( Parser_STR_str_in_str( $c, $this->quote_list ) )
{
$startpos = $count2;
$quotetype = $c;
$count2++;
$pos = $count2;
$oldpos = 0;
while ( $pos < $this->len )
{
$oldpos = $pos;
$pos = strpos( $this->css, $quotetype, $oldpos );
// Quote pas fermée
if ( $pos === false )
die( 'PARSER_CSS_ERROR::UNCLOSED_QUOTE@' . $startpos . "\n" . 'STR: ' . htmlspecialchars( $quotetype ) );
// Si le quote est le premier caractère, il ne peut être echappé.
// Ainsi le reste du code n'a pas besoin d'être exécuté.
if ( $pos === 0 )
break;
// Recherche l'échappement utilisant \
if ( ( $pos < $this->len ) && ( Parser_STR_char_is_escaped( $this->css, $pos ) ) )
{
$pos++;
continue;
}
else
{
break;
}
}
$count2 = $pos;
$count2++;
$this->array_add( 'quote', substr( $this->css, $count1, $count2 - $count1 ) );
continue;
}
// Recherche les brackets
if ( Parser_STR_str_in_str( $c, $this->bracket_list ) )
{
$count2++;
if ( Parser_STR_str_in_str( $c, '([{' ) )
$type = 'open';
else
$type = 'close';
if ( Parser_STR_str_in_str( $c, '()' ) )
$style = 'round';
elseif ( Parser_STR_str_in_str( $c, '[]' ) )
$style = 'square';
else
{
$style = 'curly';
if ( $type === 'open' )
$inbloc = true;
else
$inbloc = false;
}
$this->array_add( 'punct_bracket_' . $style . '_' . $type, $c );
continue;
}
// Recherche la ponctuation
if ( Parser_STR_str_in_str( $c, $this->punct_list ) )
{
while ( ( $count2 < $this->len ) && ( Parser_STR_str_in_str( $this->css{ $count2 }, $this->punct_list ) ) )
$count2++;
$length = $count2 - $count1;
if ( $length === 1 )
$data = $c;
else
$data = substr( $this->css, $count1, $length );
if ( $length === 1 )
{
switch ( $data )
{
case ';':
$suffix = '_property_end';
break;
case ',':
$suffix = '_listsep';
break;
case ':':
$suffix = '_2points';
break;
case '=':
$suffix = '_assing';
break;
default:
$suffix = '';
break;
}
$this->array_add( 'punct' . $suffix, $data );
}
elseif ( isset( $this->css_combined_punct[ $data ] ) )
{
$this->array_add( 'punct_assign', $data );
}
else
{
$first = $data{ 0 };
$last2 = $data{ $length - 2 } . $data{ $length - 1 };
if ( $first === ';' || $first === ',' )
{
$count2 = $count1 + 1;
$data = $first;
}
elseif ( $last2 === '/*' )
{
$count2 -= 2;
$data = substr( $this->css, $count1, $count2 - $count1 );
}
$this->array_add( 'punct', $data );
}
continue;
}
if ( $this->identifier( $c ) || $c === '@' )
{
$count2++;
while ( $count2 < $this->len && $this->identifier( $this->css{ $count2 } ) )
$count2++;
$string = substr( $this->css, $count1, $count2 - $count1 );
if ( $c === '@' )
{
$this->array_add( 'selector_at', $string );
}
elseif ( !$inbloc )
{
$pos = strpos( $string, '#' );
if ( $pos !== false )
{
if ( $pos === 0 )
$this->array_add( 'selector_id', $string );
else
{
$this->array_add( 'selector_html', substr( $string, 0, $pos ) );
$this->array_add( 'selector_id', substr( $string, $pos ) );
}
}
else
{
$pos = strpos( $string, '.' );
if ( $pos !== false )
{
if ( $pos === 0 )
$this->array_add( 'selector', $string );
else
{
$this->array_add( 'selector_html', substr( $string, 0, $pos ) );
$this->array_add( 'selector', substr( $string, $pos ) );
}
}
else
$this->array_add( 'selector_html', $string );
}
}
else
{
$this->array_add( 'alpha', $string );
}
continue;
}
$count2++;
}
if ( $this->size > 0 )
{
$prev_d = '';
$prev_t = '';
$prev_l = '';
$curr_d = '';
$curr_t = '';
$curr_l = '';
$next_d = $this->css_array[ 0 ][ 'data' ];
$next_t = $this->css_array[ 0 ][ 'type' ];
$next_l = strtolower( $next_d );
}
for ( $i = 0; $i < $this->size; $i++ )
{
$prev_d = $curr_d;
$prev_t = $curr_t;
$prev_l = $curr_l;
$curr_d = $next_d;
$curr_t = $next_t;
$curr_l = $next_l;
if ( $i + 1 < $this->size )
{
$next_d = $this->css_array[ $i + 1 ][ 'data' ];
$next_t = $this->css_array[ $i + 1 ][ 'type' ];
$next_l = strtolower( $next_d );
}
else
{
$next_d = '';
$next_t = '';
$next_l = '';
}
if ( $curr_t === 'alpha' )
{
if ( $curr_l === '!important' )
$type = 'important';
elseif ( $next_t === 'punct_2points' )
$type = 'property';
else
$type = 'value';
$this->css_array[ $i ][ 'type' ] = $type;
}
}
}
private function format_html()
{
$str = '';
$arr = array();
$arr[ 0 ] = '';
$arr[ 1 ] = '';
$arr[ 2 ] = '';
$arr[ 3 ] = $this->css_array[ 0 ][ 'type' ];
$inbloc = false;
for ( $i = 0; $i < $this->size; $i++ )
{
$indent = 0;
$before = '';
$after = '';
if ( $i + 1 < $this->size )
$arr[ 4 ] = $this->css_array[ $i + 1 ][ 'type' ];
else
$arr[ 4 ] = '';
for ( $j = 0; $j < 4; $j++ )
$arr[ $j ] = $arr[ $j + 1 ];
switch ( $arr[ 2 ] )
{
case 'comment':
$after = '<br />' . "\n";
break;
case 'important':
$before = ' ';
break;
case 'property':
$this->css_array[ $i ][ 'data' ] = strtolower( $this->css_array[ $i ][ 'data' ] );
break;
case 'punct':
$before = ' ';
if ( $arr[ 3 ] !== 'digit_int' )
$after = ' ';
break;
case 'punct_2points':
if ( $inbloc )
$after = ' ';
break;
case 'punct_bracket_curly_close':
$inbloc = false;
if ( $arr[ 1 ] !== 'punct_bracket_curly_open' )
$before = '</div>';
$after = '<br /><br />' . "\n";
break;
case 'punct_bracket_curly_open':
$inbloc = true;
$before = ' ';
if ( $arr[ 3 ] !== 'punct_bracket_curly_close' )
$after = '<div class="indent1">';
else
$after = ' ';
break;
case 'punct_bracket_round_close':
$before = ' ';
if ( $arr[ 3 ] !== 'punct_property_end' )
$after = ' ';
break;
case 'punct_bracket_round_open':
$after = ' ';
break;
case 'punct_listsep':
$after = ' ';
break;
case 'punct_property_end':
if ( $arr[ 3 ] !== 'comment' )
$after = '<br />' . "\n";
break;
case 'quote':
if ( $arr[ 3 ] !== 'punct_bracket_square_close' && $arr[ 3 ] !== 'punct_listsep' && $arr[ 3 ] !== 'punct_property_end' )
$after = ' ';
break;
case 'selector':
break;
case 'selector_at':
$after = ' ';
break;
case 'selector_html':
if ( $arr[ 3 ] === 'selector_html' )
$after = ' ';
break;
case 'selector_id':
break;
case 'value':
if ( $arr[ 3 ] === 'value' )
$after = ' ';
break;
}
$str .= $before . $this->get_html( $this->css_array[ $i ] ) . $after;
}
$this->css = &$str;
}
private function get_html( $arr )
{
$class = '';
if ( substr( $arr[ 'type' ], 0, 5 ) === 'punct' )
{
$pos = strpos( $arr[ 'type' ], '_' );
if ( $pos !== false )
$class = 'css_' . substr( $arr[ 'type' ], 0, $pos );
else
$class = 'css_' . $arr[ 'type' ];
}
else
{
$class = 'css_' . $arr[ 'type' ];
}
return '<span class="' . $class . '">' . htmlspecialchars( $arr[ 'data' ], ENT_NOQUOTES ) . '</span>';
}
private function array_add( $type, $data )
{
$this->css_array[] = array
(
'type' => $type,
'data' => $data,
);
$this->size++;
}
}
L'exemple
$css =& new CSS_Parser( '
div#menu {
position: absolute; /* placement du menu, à modifier selon vos besoins */
top: 0% !important;
left: 0;
color:red;
background: url( img/smth.png );
}
span[class|=truc] {
color: blue;
}
a[href="page.html"] {
}
@import url(\'style.css\');
@charset "ISO-8859-1";
');
$css->display();
Le CSS de mise en forme
Code : Tout sélectionner
.css_comment{color: #999;}
.css_punct{color: #f0f;}
.css_quote{color: #080;}
.css_property{color: #009;}
.css_selector{color: #f0f;}
.css_selector_at{color: #099; font-weight: bold;}
.css_selector_id{color: #f90;}
.css_selector_html{color: #000;}
.css_important{color: #f00; font-weight: bold;}
.css_value{color: #00f;}
.indent1{ margin-left: 1em;}