Subversion Repositories PHPX

Compare Revisions

Last modification

Ignore whitespace Rev 36 → Rev 37

/trunk/css/least/Parser.php
0,0 → 1,345
<?php
 
namespace de\pointedears\css\least;
 
require_once __DIR__ . '/../../string/parser/Lexer.php';
require_once __DIR__ . '/../../string/parser/Parser.php';
 
/**
* Parses a LEAST stylesheet into a CSS stylesheet
*
* @author Thomas 'PointedEars' Lahn
* @property-read string $compiled
*/
class Parser extends \de\pointedears\string\parser\Parser
{
/**
* CSS stylesheet code compiled from the LEAST stylesheet
*
* @var string
*/
protected $_compiled;
/**
* Array of arrays of template variables, one inner array for
* each scope level.
* @var array[array]
*/
protected $_vars = array(array());
 
/**
* The nesting level of the current variable scope.
*
* Used to hold variable definitions per nesting level.
*
* @var int
*/
protected $_scope_level = 0;
 
/**
* Cache for lookup results.
*
* The lookup cache improves parser performance with
* nested scopes. By contrast to <code>$_vars</code>, it is
* a _shallow_ storage for template variable definitions and
* represents the current _certain_ variable knowledge of the
* parser, i.e. variables that have been defined in outer scopes
* and the same scope, and whose values have been looked up in
* <code>$_vars</code> before.
*
* The lookup cache is replaced by the variables of the outer
* scope when a scope is exited because definitions in the
* outer scope may have been shadowed by the inner scope.
* Note that definitions from the second-next, third-next
* aso. scopes need to be looked up again then.
*
* @var array
*/
protected $_lookup_cache = array();
 
/**
* Mixins
* @var array
*/
protected $_mixins = array();
/**
* <code>true</code> if the parser is parsing a mix-in
* @var boolean
*/
protected $_in_mixin = false;
 
/**
* The currently parsed mix-in
* @var Mixin
*/
protected $_current_mixin;
 
/**
* The nesting level in the currently parsed mix-in, 0 again when
* it just ended
* @var int
*/
protected $_mixin_level = 0;
public function __construct ($code)
{
$lexer = new \de\pointedears\string\parser\Lexer($code);
 
$nl = 'n';
$escape = '\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f]';
$lexer->addTokens(array(
'(?P<PHP><\?php.*?\?>)',
"/\*[^*]*\*+([^/*][^*]*\*+)*/",
"\"([^\n\r\f\\\"]|\\{$nl}|{$escape})*\"",
"'([^\n\r\f\\']|\\{$nl}|{$escape})*'",
// '\.(?P<mixin_def>[\w-]+)\s*\((?P<params>.*?)\)\s*(?!;)',
// '\.(?P<mixin_call>[\w-]+)\s*\((?P<args>.*)?\)\s*;\\s*',
'[{}]',
// '\$(?P<name>\w+)\s*=\s*(?P<value>\'.+?\'|".+"|.+?)\s*;\s*',
// '\$(?P<ref>\w+)'
));
 
$lexer->ignoreCase = true;
$lexer->dotAll = true;
 
parent::__construct($lexer);
$this->_compiled = $code;
}
protected function defineVar($name, $value)
{
$scope_level = $this->_scope_level;
 
if (defined('DEBUG') && DEBUG > 0)
{
echo "<?php // Least.php: Found definition \${$name} = `{$value}'"
. " at scope level {$scope_level} ?>\n";
}
 
$this->_vars[$scope_level][$name] = $value;
$this->_lookup_cache[$name] = $value;
}
 
/**
* Resolves a template variable reference against the template scope chain
* @param string $ref
* @return mixed
*/
protected function resolveVar ($name)
{
$scope_level = $this->_scope_level + 1;
$orig_level = $scope_level - 1;
 
if (defined('DEBUG') && DEBUG > 0)
{
echo "<?php // Least.php: Found reference \${$name} at scope level "
. ($scope_level - 1) . " ?>\n";
}
 
/* Try the lookup cache for this scope level first */
if (array_key_exists($name, $this->_lookup_cache))
{
$value = $this->_lookup_cache[$name];
 
if (defined('DEBUG') && DEBUG > 1)
{
echo "<?php // Least.php: Resolved reference \${$name}"
. " at scope level {$orig_level}"
. " from lookup cache, value `{$value}' ?>\n";
}
 
return $value;
}
 
while ($scope_level--)
{
if (array_key_exists($name, $this->_vars[$scope_level]))
{
$value = $this->_vars[$scope_level][$name];
$this->_lookup_cache[$name] = $value;
 
if (defined('DEBUG') && DEBUG > 1)
{
echo "<?php // Least.php: Resolved reference \${$name}"
. " from scope level {$orig_level}"
. " at scope level {$scope_level}, value `{$value}' ?>\n";
}
 
return $value;
}
}
 
if (defined('DEBUG') && DEBUG > 0)
{
echo "<?php // Least.php: WARNING: Unresolved reference \${$name}"
. " at scope level {$orig_level}. Variable not in scope? ?>\n";
}
}
 
protected function defineMixin($name, $params)
{
$scope_level = $this->_scope_level;
 
if (defined('DEBUG') && DEBUG > 0)
{
echo "<?php // Least.php: Found mixin definition {$name}($params)"
. " at scope level {$scope_level}. ?>\n";
}
 
$mixin = new Mixin($params);
$this->_mixins[$name] = $mixin;
 
return $mixin;
}
 
protected function callMixin($name, $args)
{
return $this->$_mixins[$name]->apply($args);
}
 
public function parseToken ($matches)
{
ini_set('html_errors', 0);
// var_dump($matches);
// /* Get match offset and match length */
// $match = $matches[0];
// $match_start = $match[1];
// $match_length = mb_strlen($match[0]);
 
// /* Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set) */
// $matches = array_map(function ($m) {
// return $m[0];
// }, $matches);
// $match = $matches[0];
 
// if (defined('DEBUG') && DEBUG > 1)
// {
// echo print_r(array(
// 'match_start' => $match_start,
// 'match_length' => $match_length,
// 'matches' => $matches,
// 'in_mixin' => $in_mixin,
// 'scope_level' => $this->_scope_level,
// ), true) . "\n";
// }
 
// if (isset($matches['mixin_def']) && $matches['mixin_def'])
// {
// $this->_in_mixin = true;
// $this->_current_mixin = $this->defineMixin($matches['mixin_def'], $matches['params']);
// // $code = self::replace(mb_strlen($match), '', $code, $match_start);
// // $match_length = 0;
// }
// else if (isset($matches['mixin_call']) && $matches['mixin_call'])
// {
// //return
// $this->callMixin($matches['mixin_call'], $matches['args']);
// //$code = self::replace(mb_strlen($match), '', $code, $match_start);
// //$match_length = 0;
// $mixin_start = $match_start;
// }
// else if ($match === '{')
// {
// ++$this->_scope_level;
// $this->_vars[$this->_scope_level] = array();
 
// if ($this->_in_mixin)
// {
// if ($mixin_body_start === 0)
// {
// $mixin_body_start = $match_start;
// }
 
// ++$this->_mixin_level;
// }
// }
// else if ($match === '}')
// {
// --$this->_scope_level;
// $this->_lookup_cache = $this->_vars[$this->_scope_level];
 
// if ($this->_in_mixin)
// {
// --$this->_mixin_level;
// if ($this->_mixin_level === 0)
// {
// $this->_in_mixin = false;
// $this->_current_mixin->setBody(substr($code, $mixin_start + 1, $match_start - $mixin_start - 1));
// echo '"'.print_r(substr($code, $mixin_start, $match_start - $mixin_start + 1), true) . "\"\n";
// // $code = self::replace($match_start - $mixin_start + 1, '', $code, $mixin_start);
// // $match_length = 0;
// }
// }
// }
// else if (isset($matches['name']) && $matches['name'])
// {
// $this->defineVar($matches['name'], $matches['value']);
// $code = self::replace(mb_strlen($match), '', $code, $match_start);
// $match_length = 0;
// }
// else if (isset($matches['ref']) && $matches['ref'])
// {
// $name = $matches['ref'];
// if (!$in_mixin || !in_array($name, $mixin->params, true))
// {
// $value = $this->resolveVar($matches['ref']);
// $this->_lexer->text = self::replace(mb_strlen($match), $value, $code, $match_start);
// $this->_lexer->offset -= $match_length + mb_strlen($value);
// }
// }
}
public function getCompiled ()
{
return $this->_compiled;
}
}
 
class Mixin extends \de\pointedears\Base
{
/**
* Parameters of the mixin
* @var array
*/
protected $_params;
 
/**
* Body of the mixin
* @var string
*/
protected $_body = '';
 
/**
* @param string $params
*/
public function __construct ($params, $body = '')
{
$this->_params = is_array($params)
? $params
: array_map(
function ($e) {
return preg_replace('/^\$/', '', $e);
},
preg_split('/\s*,\s*/', $params));
 
if ($body !== '')
{
$this->setBody($body);
}
}
 
public function setBody ($body)
{
$this->body = (string) $body;
}
 
/**
* @param string $arguments
*/
public function apply ($arguments)
{
return '';
}
}
/trunk/css/least/least-jit.php
0,0 → 1,17
<?php
 
$file = $_GET['src'];
 
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', @filemtime($file)) . ' GMT');
 
/* Cached resource expires in HTTP/1.1 caches immediately after last retrieval */
header('Cache-Control: max-age=0, s-maxage=0, must-revalidate, proxy-revalidate');
 
/* Cached resource expires in HTTP/1.0 caches immediately after last retrieval */
header('Expires: ' . gmdate('D, d M Y H:i:s', time() /*+ 86400*/) . ' GMT');
 
header('Content-Type: text/plain; charset=UTF-8');
 
require __DIR__ . '/LEAST.php';
$least = new de\pointedears\css\least\LEAST();
/*echo*/ $least->parse_file($file);
/trunk/css/least/Mixins.php
0,0 → 1,124
<?php
 
namespace de\pointedears\css\least;
 
/**
* Mix-ins for the LEAST extended CSS parser
*
* @author Thomas 'PointedEars' Lahn &lt;php@PointedEars.de&gt;
*/
abstract class Mixins
{
/**
* Generates a CSS property declaration whose value is a
* <code>linear-gradient()</code> function call for
* supported function name prefixes.
*
* @param string $property
* Property to be declared
* @param string $params
* Parameters to the <code>linear-gradient()</code> function
* @param array $prefixes
* Pass to override supported function name prefixes
*/
public static function linear_gradient ($property, $params,
array $prefixes = array('-moz-', '-o-', '-webkit-', ''))
{
ob_start();
foreach ($prefixes as $prefix)
{
echo "{$property}: {$prefix}linear-gradient({$params});\n";
}
ob_end_flush();
}
 
/**
* Generates a CSS property declaration whose value is a
* <code>radial-gradient()</code> function call for
* supported function name prefixes.
*
* @param string $property
* Property to be declared
* @param string $params
* Parameters to the <code>radial-gradient()</code> function
* @param array $prefixes
* Pass to override supported function name prefixes
*/
public static function radial_gradient ($property, $params,
array $prefixes = array('-moz-', '-webkit-'))
{
ob_start();
foreach ($prefixes as $prefix)
{
echo "{$property}: {$prefix}radial-gradient({$params});\n";
}
ob_end_flush();
}
/**
* Generates a CSS <code>transition</code> property declaration
* for supported property name prefixes.
*
* @param string $suffix
* Property name suffix
* @param string $value
* Property value
* @param array $prefixes
* Pass to override supported property name prefixes
*/
public static function transition ($suffix, $value,
array $prefixes = array('-moz-', '-webkit-', ''))
{
ob_start();
foreach ($prefixes as $prefix)
{
echo "{$prefix}transition{$suffix}: {$value};\n";
}
ob_end_flush();
}
 
/**
* Generates a CSS <code>@keyframes</code> section for
* supported keyword prefixes.
*
* @param string $name
* Animation name as referred by the <code>animation-name</code>
* property declaration.
* @param string $data
* Keyframes data
* @param array $prefixes
* Pass to override supported keyword prefixes
*/
public static function keyframes ($name, $data,
array $prefixes = array('-moz-', '-webkit-', ''))
{
ob_start();
foreach ($prefixes as $prefix)
{
echo "@{$prefix}keyframes {$name} {\n {$data}\n}\n";
}
ob_end_flush();
}
 
/**
* Generates a CSS <code>animation</code> property declaration
* for supported property name prefixes.
*
* @param string $suffix
* Property name suffix
* @param string $value
* Property value
* @param array $prefixes
* Pass to override supported property name prefixes
*/
public static function animation ($suffix, $value,
array $prefixes = array('-moz-', '-webkit-', ''))
{
ob_start();
foreach ($prefixes as $prefix)
{
echo "{$prefix}animation{$suffix}: {$value};\n";
}
ob_end_flush();
}
}
/trunk/css/least/LEAST.php
0,0 → 1,138
<?php
 
namespace de\pointedears\css\least;
 
require_once 'Base.php';
 
require_once 'Parser.php';
 
define('DEBUG', 2);
 
/**
* @author Thomas 'PointedEars' Lahn
*/
class LEAST
{
/**
* Default name of parsed file
*
* @var string
*/
protected $_filename;
/**
* Default name of parsed file
*
* @param string $filename
*/
public function __construct ($code = null)
{
if ($code !== null)
{
$this->_code = $code;
}
}
/**
* Gets a template variable or a property
*
* @param string $name
* @throws InvalidArgumentException
* @return mixed
*/
public function __get($name)
{
if (property_exists($this, "_$name"))
{
return $this->{"_$name"};
}
if (array_key_exists($name, $this->_vars))
{
return $this->_vars[$name];
}
throw new InvalidArgumentException("no property '{$name}'");
}
/**
* Sets a template variable
*
* @param string $name
* @param mixed $value
*/
public function __set($name, $value)
{
$this->_vars[$name] = $value;
}
/**
* Replaces characters in LEAST source code
*
* @param string $count
* Number of characters to replace
* @param string $replacement
* Replacement string
* @param string $haystack
* String to be searched
* @param int $start
* Start replace from here
* @return string
*/
protected static function replace ($count, $replacement, $haystack, $start)
{
return substr($haystack, 0, $start) . $replacement . substr($haystack, $start + $count);
}
/**
* Parses LEAST source code, replacing special tokens, and returns the result
*
* @param string[optional] $code
* @return string
*/
public function parse ($code = null)
{
if ($code === null)
{
$code = $this->_code;
}
 
static $nl = 'n';
 
\mb_internal_encoding(mb_detect_encoding($code));
$parser = new \de\pointedears\css\least\Parser($code);
$parser->parse();
 
return $parser->compiled;
// return $code;
}
/**
* Parses a LEAST file and returns the result
*
* @param string $filename
* @return string
*/
public function parse_file ($filename = null)
{
$contents = file_get_contents(
$filename === null ? $this->filename : $filename);
return $this->parse($contents);
}
/**
* Compiles a CSS file from a LEAST file
*
* @param string $source
* @param string $target
*/
public static function compile ($source, $target)
{
if (@filemtime($target) < @filemtime($source))
{
$least = new self();
file_put_contents($target, $least->parse_file($source));
}
}
}