Coloration syntaxique de CSS

Eléphant du PHP | 493 Messages

14 sept. 2005, 17:19

Je vous propose ici un colorateur syntaxique CSS. Celui-ci n'est pas destiné à un site Web ! En effet, son algorithme de coloration est bien trop couteux en ressources, bien que pour les petits sites il ne poserait aucun probleme. Toutefois, son utilisation dans une application, ou un script destiné à peu d'utilisateur (moins de 10 utilisateurs simultannés).

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;}

Eléphant du PHP | 493 Messages

14 sept. 2005, 17:21

Je précise. Les fonctions ne sont PAS définie. Vous devez donc renseigner le tableau $css_function_name.