123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848 |
- <?php
- /* SVN FILE: $Id: SassParser.php 118 2010-09-21 09:45:11Z chris.l.yates@gmail.com $ */
- /**
- * SassParser class file.
- * See the {@link http://sass-lang.com/docs Sass documentation}
- * for details of Sass.
- *
- * Credits:
- * This is a port of Sass to PHP. All the genius comes from the people that
- * invented and develop Sass; in particular:
- * + {@link http://hamptoncatlin.com/ Hampton Catlin},
- * + {@link http://nex-3.com/ Nathan Weizenbaum},
- * + {@link http://chriseppstein.github.com/ Chris Eppstein}
- *
- * The bugs are mine. Please report any found at {@link http://code.google.com/p/phamlp/issues/list}
- *
- * @author Chris Yates <chris.l.yates@gmail.com>
- * @copyright Copyright (c) 2010 PBM Web Development
- * @license http://phamlp.googlecode.com/files/license.txt
- * @package PHamlP
- * @subpackage Sass
- */
- require_once('SassFile.php');
- require_once('SassException.php');
- require_once('tree/SassNode.php');
- /**
- * SassParser class.
- * Parses {@link http://sass-lang.com/ .sass and .sccs} files.
- * @package PHamlP
- * @subpackage Sass
- */
- class SassParser {
- /**#@+
- * Default option values
- */
- const CACHE = true;
- const CACHE_LOCATION = './sass-cache';
- const CSS_LOCATION = './css';
- const TEMPLATE_LOCATION = './sass-templates';
- const BEGIN_COMMENT = '/';
- const BEGIN_CSS_COMMENT = '/*';
- const END_CSS_COMMENT = '*/';
- const BEGIN_SASS_COMMENT= '//';
- const BEGIN_INTERPOLATION = '#';
- const BEGIN_INTERPOLATION_BLOCK = '#{';
- const BEGIN_BLOCK = '{';
- const END_BLOCK = '}';
- const END_STATEMENT = ';';
- const DOUBLE_QUOTE = '"';
- const SINGLE_QUOTE = "'";
- /**
- * @var string the character used for indenting
- * @see indentChars
- * @see indentSpaces
- */
- private $indentChar;
- /**
- * @var array allowable characters for indenting
- */
- private $indentChars = array(' ', "\t");
- /**
- * @var integer number of spaces for indentation.
- * Used to calculate {@link Level} if {@link indentChar} is space.
- */
- private $indentSpaces = 2;
-
- /**
- * @var string source
- */
- private $source;
-
- /**#@+
- * Option
- */
- /**
- * cache:
- * @var boolean Whether parsed Sass files should be cached, allowing greater
- * speed.
- *
- * Defaults to true.
- */
- private $cache;
-
- /**
- * cache_location:
- * @var string The path where the cached sassc files should be written to.
- *
- * Defaults to './sass-cache'.
- */
- private $cache_location;
-
- /**
- * css_location:
- * @var string The path where CSS output should be written to.
- *
- * Defaults to './css'.
- */
- private $css_location;
-
- /**
- * debug_info:
- * @var boolean When true the line number and file where a selector is defined
- * is emitted into the compiled CSS in a format that can be understood by the
- * {@link https://addons.mozilla.org/en-US/firefox/addon/103988/
- * FireSass Firebug extension}.
- * Disabled when using the compressed output style.
- *
- * Defaults to false.
- * @see style
- */
- private $debug_info;
-
- /**
- * extensions:
- * @var array Sass extensions, e.g. Compass. An associative array of the form
- * $name => $options where $name is the name of the extension and $options
- * is an array of name=>value options pairs.
- */
- protected $extensions;
-
- /**
- * filename:
- * @var string The filename of the file being rendered.
- * This is used solely for reporting errors.
- */
- protected $filename;
-
- /**
- * function_paths:
- * @var array An array of filesystem paths which should be searched for
- * SassScript functions.
- */
- private $function_paths;
-
- /**
- * line:
- * @var integer The number of the first line of the Sass template. Used for
- * reporting line numbers for errors. This is useful to set if the Sass
- * template is embedded.
- *
- * Defaults to 1.
- */
- private $line;
-
- /**
- * line_numbers:
- * @var boolean When true the line number and filename where a selector is
- * defined is emitted into the compiled CSS as a comment. Useful for debugging
- * especially when using imports and mixins.
- * Disabled when using the compressed output style or the debug_info option.
- *
- * Defaults to false.
- * @see debug_info
- * @see style
- */
- private $line_numbers;
-
- /**
- * load_paths:
- * @var array An array of filesystem paths which should be searched for
- * Sass templates imported with the @import directive.
- *
- * Defaults to './sass-templates'.
- */
- private $load_paths;
-
- /**
- * property_syntax:
- * @var string Forces the document to use one syntax for
- * properties. If the correct syntax isn't used, an error is thrown.
- * Value can be:
- * + new - forces the use of a colon or equals sign after the property name.
- * For example color: #0f3 or width: $main_width.
- * + old - forces the use of a colon before the property name.
- * For example: :color #0f3 or :width = $main_width.
- *
- * By default, either syntax is valid.
- *
- * Ignored for SCSS files which alaways use the new style.
- */
- private $property_syntax;
-
- /**
- * quiet:
- * @var boolean When set to true, causes warnings to be disabled.
- * Defaults to false.
- */
- private $quiet;
-
- /**
- * style:
- * @var string the style of the CSS output.
- * Value can be:
- * + nested - Nested is the default Sass style, because it reflects the
- * structure of the document in much the same way Sass does. Each selector
- * and rule has its own line with indentation is based on how deeply the rule
- * is nested. Nested style is very useful when looking at large CSS files as
- * it allows you to very easily grasp the structure of the file without
- * actually reading anything.
- * + expanded - Expanded is the typical human-made CSS style, with each selector
- * and property taking up one line. Selectors are not indented; properties are
- * indented within the rules.
- * + compact - Each CSS rule takes up only one line, with every property defined
- * on that line. Nested rules are placed with each other while groups of rules
- * are separated by a blank line.
- * + compressed - Compressed has no whitespace except that necessary to separate
- * selectors and properties. It's not meant to be human-readable.
- *
- * Defaults to 'nested'.
- */
- private $style;
-
- /**
- * syntax:
- * @var string The syntax of the input file.
- * 'sass' for the indented syntax and 'scss' for the CSS-extension syntax.
- *
- * This is set automatically when parsing a file, else defaults to 'sass'.
- */
- private $syntax;
- /**
- * template_location:
- * @var string Path to the root sass template directory for your
- * application.
- */
- private $template_location;
- /**
- * vendor_properties:
- * If enabled a property need only be written in the standard form and vendor
- * specific versions will be added to the style sheet.
- * @var mixed array: vendor properties, merged with the built-in vendor
- * properties, to automatically apply.
- * Boolean true: use built in vendor properties.
- *
- * Defaults to vendor_properties disabled.
- * @see _vendorProperties
- */
- private $vendor_properties = array();
-
- /**#@-*/
- /**
- * Defines the build-in vendor properties
- * @var array built-in vendor properties
- * @see vendor_properties
- */
- private $_vendorProperties = array(
- 'border-radius' => array(
- '-moz-border-radius',
- '-webkit-border-radius',
- '-khtml-border-radius'
- ),
- 'border-top-right-radius' => array(
- '-moz-border-radius-topright',
- '-webkit-border-top-right-radius',
- '-khtml-border-top-right-radius'
- ),
- 'border-bottom-right-radius' => array(
- '-moz-border-radius-bottomright',
- '-webkit-border-bottom-right-radius',
- '-khtml-border-bottom-right-radius'
- ),
- 'border-bottom-left-radius' => array(
- '-moz-border-radius-bottomleft',
- '-webkit-border-bottom-left-radius',
- '-khtml-border-bottom-left-radius'
- ),
- 'border-top-left-radius' => array(
- '-moz-border-radius-topleft',
- '-webkit-border-top-left-radius',
- '-khtml-border-top-left-radius'
- ),
- 'box-shadow' => array('-moz-box-shadow', '-webkit-box-shadow'),
- 'box-sizing' => array('-moz-box-sizing', '-webkit-box-sizing'),
- 'opacity' => array('-moz-opacity', '-webkit-opacity', '-khtml-opacity'),
- );
- /**
- * Constructor.
- * Sets parser options
- * @param array $options
- * @return SassParser
- */
- public function __construct($options = array()) {
- if (!is_array($options)) {
- throw new SassException('{what} must be a {type}', array('{what}'=>'options', '{type}'=>'array'));
- }
- if (!empty($options['language'])) {
- Phamlp::$language = $options['language'];
- }
-
- if (!empty($options['extensions'])) {
- foreach ($options['extensions'] as $extension=>$extOptions) {
- include dirname(__FILE__).DIRECTORY_SEPARATOR.'extensions'.DIRECTORY_SEPARATOR.$extension.DIRECTORY_SEPARATOR.'config.php';
- $configClass = 'SassExtentions'.$extension.'Config';
- $config = new $configClass;
- $config->config($extOptions);
-
- $lp = dirname(__FILE__).DIRECTORY_SEPARATOR.'extensions'.DIRECTORY_SEPARATOR.$extension.DIRECTORY_SEPARATOR.'frameworks';
- $fp = dirname(__FILE__).DIRECTORY_SEPARATOR.'extensions'.DIRECTORY_SEPARATOR.$extension.DIRECTORY_SEPARATOR.'functions';
- $options['load_paths'] = (empty($options['load_paths']) ?
- array($lp) : array_merge($options['load_paths'], $lp));
- $options['function_paths'] = (empty($options['function_paths']) ?
- array($fp) : array_merge($options['function_paths'], $fp));
- }
- }
-
- if (!empty($options['vendor_properties'])) {
- if ($options['vendor_properties'] === true) {
- $this->vendor_properties = $this->_vendorProperties;
- }
- elseif (is_array($options['vendor_properties'])) {
- $this->vendor_properties = array_merge($this->vendor_properties, $this->_vendorProperties);
- }
- }
- unset($options['language'], $options['vendor_properties']);
-
- $defaultOptions = array(
- 'cache' => self::CACHE,
- 'cache_location' => dirname(__FILE__) . DIRECTORY_SEPARATOR . self::CACHE_LOCATION,
- 'css_location' => dirname(__FILE__) . DIRECTORY_SEPARATOR . self::CSS_LOCATION,
- 'debug_info' => false,
- 'filename' => array('dirname' => '', 'basename' => ''),
- 'function_paths' => array(),
- 'load_paths' => array(dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_LOCATION),
- 'line' => 1,
- 'line_numbers' => false,
- 'style' => SassRenderer::STYLE_NESTED,
- 'syntax' => SassFile::SASS
- );
-
- foreach (array_merge($defaultOptions, $options) as $name=>$value) {
- if (property_exists($this, $name)) {
- $this->$name = $value;
- }
- }
- }
-
- /**
- * Getter.
- * @param string name of property to get
- * @return mixed return value of getter function
- */
- public function __get($name) {
- $getter = 'get' . ucfirst($name);
- if (method_exists($this, $getter)) {
- return $this->$getter();
- }
- throw new SassException('No getter function for {what}', array('{what}'=>$name));
- }
-
- public function getCache() {
- return $this->cache;
- }
-
- public function getCache_location() {
- return $this->cache_location;
- }
-
- public function getCss_location() {
- return $this->css_location;
- }
-
- public function getDebug_info() {
- return $this->debug_info;
- }
-
- public function getFilename() {
- return $this->filename;
- }
-
- public function getLine() {
- return $this->line;
- }
-
- public function getSource() {
- return $this->source;
- }
-
- public function getLine_numbers() {
- return $this->line_numbers;
- }
-
- public function getFunction_paths() {
- return $this->function_paths;
- }
-
- public function getLoad_paths() {
- return $this->load_paths;
- }
-
- public function getProperty_syntax() {
- return $this->property_syntax;
- }
-
- public function getQuiet() {
- return $this->quiet;
- }
-
- public function getStyle() {
- return $this->style;
- }
-
- public function getSyntax() {
- return $this->syntax;
- }
-
- public function getTemplate_location() {
- return $this->template_location;
- }
-
- public function getVendor_properties() {
- return $this->vendor_properties;
- }
-
- public function getOptions() {
- return array(
- 'cache' => $this->cache,
- 'cache_location' => $this->cache_location,
- 'css_location' => $this->css_location,
- 'filename' => $this->filename,
- 'function_paths' => $this->function_paths,
- 'line' => $this->line,
- 'line_numbers' => $this->line_numbers,
- 'load_paths' => $this->load_paths,
- 'property_syntax' => $this->property_syntax,
- 'quiet' => $this->quiet,
- 'style' => $this->style,
- 'syntax' => $this->syntax,
- 'template_location' => $this->template_location,
- 'vendor_properties' => $this->vendor_properties
- );
- }
- /**
- * Parse a sass file or Sass source code and returns the CSS.
- * @param string name of source file or Sass source
- * @return string CSS
- */
- public function toCss($source, $isFile = true) {
- return $this->parse($source, $isFile)->render();
- }
- /**
- * Parse a sass file or Sass source code and
- * returns the document tree that can then be rendered.
- * The file will be searched for in the directories specified by the
- * load_paths option.
- * If caching is enabled a cached version will be used if possible or the
- * compiled version cached if not.
- * @param string name of source file or Sass source
- * @return SassRootNode Root node of document tree
- */
- public function parse($source, $isFile = true) {
- if ($isFile) {
- $this->filename = SassFile::getFile($source, $this);
-
- if ($isFile) {
- $this->syntax = substr($this->filename, -4);
- }
- elseif ($this->syntax !== SassFile::SASS && $this->syntax !== SassFile::SCSS) {
- throw new SassException('Invalid {what}', array('{what}'=>'syntax option'));
- }
- if ($this->cache) {
- $cached = SassFile::getCachedFile($this->filename, $this->cache_location);
- if ($cached !== false) {
- return $cached;
- }
- }
-
- $tree = $this->toTree(file_get_contents($this->filename));
- if ($this->cache) {
- SassFile::setCachedFile($tree, $this->filename, $this->cache_location);
- }
- return $tree;
- }
- else {
- return $this->toTree($source);
- }
- }
- /**
- * Parse Sass source into a document tree.
- * If the tree is already created return that.
- * @param string Sass source
- * @return SassRootNode the root of this document tree
- */
- private function toTree($source) {
- if ($this->syntax === SassFile::SASS) {
- $this->source = explode("\n", $source);
- $this->setIndentChar();
- }
- else {
- $this->source = $source;
- }
- unset($source);
- $root = new SassRootNode($this);
- $this->buildTree($root);
- return $root;
- }
- /**
- * Builds a parse tree under the parent node.
- * Called recursivly until the source is parsed.
- * @param SassNode the node
- */
- private function buildTree($parent) {
- $node = $this->getNode($parent);
- while (is_object($node) && $node->isChildOf($parent)) {
- $parent->addChild($node);
- $node = $this->buildTree($node);
- }
- return $node;
- }
- /**
- * Creates and returns the next SassNode.
- * The tpye of SassNode depends on the content of the SassToken.
- * @return SassNode a SassNode of the appropriate type. Null when no more
- * source to parse.
- */
- private function getNode($node) {
- $token = $this->getToken();
- if (empty($token)) return null;
- switch (true) {
- case SassDirectiveNode::isa($token):
- return $this->parseDirective($token, $node);
- break;
- case SassCommentNode::isa($token):
- return new SassCommentNode($token);
- break;
- case SassVariableNode::isa($token):
- return new SassVariableNode($token);
- break;
- case SassPropertyNode::isa($token, $this->property_syntax):
- return new SassPropertyNode($token, $this->property_syntax);
- break;
- case SassMixinDefinitionNode::isa($token):
- if ($this->syntax === SassFile::SCSS) {
- throw new SassException('Mixin {which} shortcut not allowed in SCSS', array('{which}'=>'definition'), $this);
- }
- return new SassMixinDefinitionNode($token);
- break;
- case SassMixinNode::isa($token):
- if ($this->syntax === SassFile::SCSS) {
- throw new SassException('Mixin {which} shortcut not allowed in SCSS', array('{which}'=>'include'), $this);
- }
- return new SassMixinNode($token);
- break;
- default:
- return new SassRuleNode($token);
- break;
- } // switch
- }
-
- /**
- * Returns a token object that contains the next source statement and
- * meta data about it.
- * @return object
- */
- private function getToken() {
- return ($this->syntax === SassFile::SASS ? $this->sass2Token() : $this->scss2Token());
- }
-
- /**
- * Returns an object that contains the next source statement and meta data
- * about it from SASS source.
- * Sass statements are passed over. Statements spanning multiple lines, e.g.
- * CSS comments and selectors, are assembled into a single statement.
- * @return object Statement token. Null if end of source.
- */
- private function sass2Token() {
- $statement = ''; // source line being tokenised
- $token = null;
-
- while (is_null($token) && !empty($this->source)) {
- while (empty($statement) && !empty($this->source)) {
- $source = array_shift($this->source);
- $statement = trim($source);
- $this->line++;
- }
-
- if (empty($statement)) {
- break;
- }
-
- $level = $this->getLevel($source);
-
- // Comment statements can span multiple lines
- if ($statement[0] === self::BEGIN_COMMENT) {
- // Consume Sass comments
- if (substr($statement, 0, strlen(self::BEGIN_SASS_COMMENT))
- === self::BEGIN_SASS_COMMENT) {
- unset($statement);
- while($this->getLevel($this->source[0]) > $level) {
- array_shift($this->source);
- $this->line++;
- }
- continue;
- }
- // Build CSS comments
- elseif (substr($statement, 0, strlen(self::BEGIN_CSS_COMMENT))
- === self::BEGIN_CSS_COMMENT) {
- while($this->getLevel($this->source[0]) > $level) {
- $statement .= "\n" . ltrim(array_shift($this->source));
- $this->line++;
- }
- }
- else {
- $this->source = $statement;
- throw new SassException('Illegal comment type', array(), $this);
- }
- }
- // Selector statements can span multiple lines
- elseif (substr($statement, -1) === SassRuleNode::CONTINUED) {
- // Build the selector statement
- while($this->getLevel($this->source[0]) === $level) {
- $statement .= ltrim(array_shift($this->source));
- $this->line++;
- }
- }
-
- $token = (object) array(
- 'source' => $statement,
- 'level' => $level,
- 'filename' => $this->filename,
- 'line' => $this->line - 1,
- );
- }
- return $token;
- }
- /**
- * Returns the level of the line.
- * Used for .sass source
- * @param string the source
- * @return integer the level of the source
- * @throws Exception if the source indentation is invalid
- */
- private function getLevel($source) {
- $indent = strlen($source) - strlen(ltrim($source));
- $level = $indent/$this->indentSpaces;
- if (!is_int($level) ||
- preg_match("/[^{$this->indentChar}]/", substr($source, 0, $indent))) {
- $this->source = $source;
- throw new SassException('Invalid indentation', array(), $this);
- }
- return $level;
- }
-
- /**
- * Returns an object that contains the next source statement and meta data
- * about it from SCSS source.
- * @return object Statement token. Null if end of source.
- */
- private function scss2Token() {
- static $srcpos = 0; // current position in the source stream
- static $srclen; // the length of the source stream
-
- $statement = '';
- $token = null;
- if (empty($srclen)) {
- $srclen = strlen($this->source);
- }
- while (is_null($token) && $srcpos < $srclen) {
- $c = $this->source[$srcpos++];
- switch ($c) {
- case self::BEGIN_COMMENT:
- if (substr($this->source, $srcpos-1, strlen(self::BEGIN_SASS_COMMENT))
- === self::BEGIN_SASS_COMMENT) {
- while ($this->source[$srcpos++] !== "\n");
- $statement .= "\n";
- }
- elseif (substr($this->source, $srcpos-1, strlen(self::BEGIN_CSS_COMMENT))
- === self::BEGIN_CSS_COMMENT) {
- if (ltrim($statement)) {
- throw new SassException('Invalid {what}', array('{what}'=>'comment'), (object) array(
- 'source' => $statement,
- 'filename' => $this->filename,
- 'line' => $this->line,
- ));
- }
- $statement .= $c.$this->source[$srcpos++];
- while (substr($this->source, $srcpos, strlen(self::END_CSS_COMMENT))
- !== self::END_CSS_COMMENT) {
- $statement .= $this->source[$srcpos++];
- }
- $srcpos += strlen(self::END_CSS_COMMENT);
- $token = $this->createToken($statement.self::END_CSS_COMMENT);
- }
- else {
- $statement .= $c;
- }
- break;
- case self::DOUBLE_QUOTE:
- case self::SINGLE_QUOTE:
- $statement .= $c;
- while ($this->source[$srcpos] !== $c) {
- $statement .= $this->source[$srcpos++];
- }
- $statement .= $this->source[$srcpos++];
- break;
- case self::BEGIN_INTERPOLATION:
- $statement .= $c;
- if (substr($this->source, $srcpos-1, strlen(self::BEGIN_INTERPOLATION_BLOCK))
- === self::BEGIN_INTERPOLATION_BLOCK) {
- while ($this->source[$srcpos] !== self::END_BLOCK) {
- $statement .= $this->source[$srcpos++];
- }
- $statement .= $this->source[$srcpos++];
- }
- break;
- case self::BEGIN_BLOCK:
- case self::END_BLOCK:
- case self::END_STATEMENT:
- $token = $this->createToken($statement . $c);
- if (is_null($token)) $statement = '';
- break;
- default:
- $statement .= $c;
- break;
- }
- }
-
- if (is_null($token))
- $srclen = $srcpos = 0;
- return $token;
- }
-
- /**
- * Returns an object that contains the source statement and meta data about
- * it.
- * If the statement is just and end block we update the meta data and return null.
- * @param string source statement
- * @return SassToken
- */
- private function createToken($statement) {
- static $level = 0;
-
- $this->line += substr_count($statement, "\n");
- $statement = trim($statement);
- if (substr($statement, 0, strlen(self::BEGIN_CSS_COMMENT)) !== self::BEGIN_CSS_COMMENT) {
- $statement = str_replace(array("\n","\r"), '', $statement);
- }
- $last = substr($statement, -1);
- // Trim the statement removing whitespace, end statement (;), begin block ({), and (unless the statement ends in an interpolation block) end block (})
- $statement = rtrim($statement, ' '.self::BEGIN_BLOCK.self::END_STATEMENT);
- $statement = (preg_match('/#\{.+?\}$/i', $statement) ? $statement : rtrim($statement, self::END_BLOCK));
- $token = ($statement ? (object) array(
- 'source' => $statement,
- 'level' => $level,
- 'filename' => $this->filename,
- 'line' => $this->line,
- ) : null);
- $level += ($last === self::BEGIN_BLOCK ? 1 : ($last === self::END_BLOCK ? -1 : 0));
- return $token;
- }
- /**
- * Parses a directive
- * @param SassToken token to parse
- * @param SassNode parent node
- * @return SassNode a Sass directive node
- */
- private function parseDirective($token, $parent) {
- switch (SassDirectiveNode::extractDirective($token)) {
- case '@extend':
- return new SassExtendNode($token);
- break;
- case '@mixin':
- return new SassMixinDefinitionNode($token);
- break;
- case '@include':
- return new SassMixinNode($token);
- break;
- case '@import':
- if ($this->syntax == SassFile::SASS) {
- $i = 0;
- $source = '';
- while (!empty($this->source) && empty($source)) {
- $source = $this->source[$i++];
- }
- if (!empty($source) && $this->getLevel($source) > $token->level) {
- throw new SassException('Nesting not allowed beneath {what}', array('{what}'=>'@import directive'), $token);
- }
- }
- return new SassImportNode($token);
- break;
- case '@for':
- return new SassForNode($token);
- break;
- case '@if':
- return new SassIfNode($token);
- break;
- case '@else': // handles else and else if directives
- return new SassElseNode($token);
- break;
- case '@do':
- case '@while':
- return new SassWhileNode($token);
- break;
- case '@debug':
- return new SassDebugNode($token);
- break;
- case '@warn':
- return new SassDebugNode($token, true);
- break;
- default:
- return new SassDirectiveNode($token);
- break;
- }
- }
- /**
- * Determine the indent character and indent spaces.
- * The first character of the first indented line determines the character.
- * If this is a space the number of spaces determines the indentSpaces; this
- * is always 1 if the indent character is a tab.
- * Only used for .sass files.
- * @throws SassException if the indent is mixed or
- * the indent character can not be determined
- */
- private function setIndentChar() {
- foreach ($this->source as $l=>$source) {
- if (!empty($source) && in_array($source[0], $this->indentChars)) {
- $this->indentChar = $source[0];
- for ($i = 0, $len = strlen($source); $i < $len && $source[$i] == $this->indentChar; $i++);
- if ($i < $len && in_array($source[$i], $this->indentChars)) {
- $this->line = ++$l;
- $this->source = $source;
- throw new SassException('Mixed indentation not allowed', array(), $this);
- }
- $this->indentSpaces = ($this->indentChar == ' ' ? $i : 1);
- return;
- }
- } // foreach
- $this->indentChar = ' ';
- $this->indentSpaces = 2;
- }
- }
|