-../leafo/scssphp/bin/pscss
\ No newline at end of file
+#!/usr/bin/env sh
+
+dir=$(cd "${0%[/\\]*}" > /dev/null; cd "../leafo/scssphp/bin" && pwd)
+
+if [ -d /proc/cygdrive ]; then
+ case $(which php) in
+ $(readlink -n /proc/cygdrive)/*)
+ # We are in Cygwin using Windows php, so the path must be translated
+ dir=$(cygpath -m "$dir");
+ ;;
+ esac
+fi
+
+"${dir}/pscss" "$@"
--- /dev/null
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/../leafo/scssphp/bin/pscss
+php "%BIN_TARGET%" %*
}
},
"require": {
- "ezyang/htmlpurifier": "4.10.*",
+ "ezyang/htmlpurifier": "4.12.*",
"erusev/parsedown": "1.7.*",
"pelago/emogrifier": "2.1.*",
"chrisjean/php-ico": "1.0.*",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b95904d82ddc37f1e3ada56aa0d9637f",
+ "content-hash": "0b588fa9bae7b0d713b4dadff7fbe829",
"packages": [
{
"name": "chrisjean/php-ico",
},
{
"name": "erusev/parsedown",
- "version": "v1.7.2",
+ "version": "1.7.3",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf"
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf",
- "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
"shasum": ""
},
"require": {
"markdown",
"parser"
],
- "time": "2019-03-17T17:19:46+00:00"
+ "time": "2019-03-17T18:48:37+00:00"
},
{
"name": "ezyang/htmlpurifier",
- "version": "v4.10.0",
+ "version": "v4.12.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
- "reference": "d85d39da4576a6934b72480be6978fb10c860021"
+ "reference": "a617e55bc62a87eec73bd456d146d134ad716f03"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021",
- "reference": "d85d39da4576a6934b72480be6978fb10c860021",
+ "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a617e55bc62a87eec73bd456d146d134ad716f03",
+ "reference": "a617e55bc62a87eec73bd456d146d134ad716f03",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
- "simpletest/simpletest": "^1.1"
+ "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
},
"type": "library",
"autoload": {
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL"
+ "LGPL-2.1-or-later"
],
"authors": [
{
"keywords": [
"html"
],
- "time": "2018-02-23T01:58:20+00:00"
+ "time": "2019-10-28T03:44:26+00:00"
},
{
"name": "leafo/scssphp",
- "version": "v0.7.7",
+ "version": "v0.7.8",
"source": {
"type": "git",
"url": "https://github.com/leafo/scssphp.git",
- "reference": "1d656f8c02a3a69404bba6b28ec4e06edddf0f49"
+ "reference": "a384906af3d078e98b089d7d36f6ceab8703f7ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/leafo/scssphp/zipball/1d656f8c02a3a69404bba6b28ec4e06edddf0f49",
- "reference": "1d656f8c02a3a69404bba6b28ec4e06edddf0f49",
+ "url": "https://api.github.com/repos/leafo/scssphp/zipball/a384906af3d078e98b089d7d36f6ceab8703f7ff",
+ "reference": "a384906af3d078e98b089d7d36f6ceab8703f7ff",
"shasum": ""
},
"require": {
"scss",
"stylesheet"
],
- "time": "2018-07-22T01:22:08+00:00"
+ "time": "2019-04-24T18:10:10+00:00"
},
{
"name": "pear/net_idna2",
},
{
"name": "symfony/css-selector",
- "version": "v3.4.23",
+ "version": "v3.4.35",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf"
+ "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
- "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
+ "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
"shasum": ""
},
"require": {
"MIT"
],
"authors": [
- {
- "name": "Jean-François Simon",
- "email": "jeanfrancois.simon@sensiolabs.com"
- },
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T09:39:14+00:00"
+ "time": "2019-10-01T11:57:37+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.11.0",
+ "version": "v1.12.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
+ "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
- "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+ "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11-dev"
+ "dev-master": "1.12-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2019-02-06T07:57:58+00:00"
+ "time": "2019-08-06T08:03:45+00:00"
},
{
"name": "true/punycode",
},
{
"name": "erusev/parsedown",
- "version": "v1.7.2",
- "version_normalized": "1.7.2.0",
+ "version": "1.7.3",
+ "version_normalized": "1.7.3.0",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf"
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf",
- "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
+ "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7",
"shasum": ""
},
"require": {
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
- "time": "2019-03-17T17:19:46+00:00",
+ "time": "2019-03-17T18:48:37+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
},
{
"name": "ezyang/htmlpurifier",
- "version": "v4.10.0",
- "version_normalized": "4.10.0.0",
+ "version": "v4.12.0",
+ "version_normalized": "4.12.0.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
- "reference": "d85d39da4576a6934b72480be6978fb10c860021"
+ "reference": "a617e55bc62a87eec73bd456d146d134ad716f03"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021",
- "reference": "d85d39da4576a6934b72480be6978fb10c860021",
+ "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a617e55bc62a87eec73bd456d146d134ad716f03",
+ "reference": "a617e55bc62a87eec73bd456d146d134ad716f03",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
- "simpletest/simpletest": "^1.1"
+ "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
},
- "time": "2018-02-23T01:58:20+00:00",
+ "time": "2019-10-28T03:44:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPL"
+ "LGPL-2.1-or-later"
],
"authors": [
{
},
{
"name": "leafo/scssphp",
- "version": "v0.7.7",
- "version_normalized": "0.7.7.0",
+ "version": "v0.7.8",
+ "version_normalized": "0.7.8.0",
"source": {
"type": "git",
"url": "https://github.com/leafo/scssphp.git",
- "reference": "1d656f8c02a3a69404bba6b28ec4e06edddf0f49"
+ "reference": "a384906af3d078e98b089d7d36f6ceab8703f7ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/leafo/scssphp/zipball/1d656f8c02a3a69404bba6b28ec4e06edddf0f49",
- "reference": "1d656f8c02a3a69404bba6b28ec4e06edddf0f49",
+ "url": "https://api.github.com/repos/leafo/scssphp/zipball/a384906af3d078e98b089d7d36f6ceab8703f7ff",
+ "reference": "a384906af3d078e98b089d7d36f6ceab8703f7ff",
"shasum": ""
},
"require": {
"phpunit/phpunit": "~4.6",
"squizlabs/php_codesniffer": "~2.5"
},
- "time": "2018-07-22T01:22:08+00:00",
+ "time": "2019-04-24T18:10:10+00:00",
"bin": [
"bin/pscss"
],
},
{
"name": "symfony/css-selector",
- "version": "v3.4.23",
- "version_normalized": "3.4.23.0",
+ "version": "v3.4.35",
+ "version_normalized": "3.4.35.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf"
+ "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
- "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
+ "reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
},
- "time": "2019-01-16T09:39:14+00:00",
+ "time": "2019-10-01T11:57:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
"MIT"
],
"authors": [
- {
- "name": "Jean-François Simon",
- "email": "jeanfrancois.simon@sensiolabs.com"
- },
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.11.0",
- "version_normalized": "1.11.0.0",
+ "version": "v1.12.0",
+ "version_normalized": "1.12.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
+ "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
- "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+ "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
"shasum": ""
},
"require": {
"suggest": {
"ext-mbstring": "For best performance"
},
- "time": "2019-02-06T07:57:58+00:00",
+ "time": "2019-08-06T08:03:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11-dev"
+ "dev-master": "1.12-dev"
}
},
"installation-source": "dist",
. Internal change
==========================
+4.12.0, released 2019-10-27
+! PHP 7.4 is supported, thank you Witold Wasiczko, Mateuz Turcza and
+ Edi Modrić
+- PHPDocs for HTMLModule::addElement() and Bool attr are fixed (thanks
+ Mateusz)
+
+4.11.0, released 2019-07-14
+# SafeScripting now matches case-sensitively against its whitelist (previously it was
+ case-insensitive.) Thanks Dimitri Gritsajuk <gritsajuk.dimitri@gmail.com>
+ for reporting.
+! New directive %Core.AllowParseManyTags which allows parsing of many nested tags.
+ Thanks M. Suzuki <msuzuki1986@gmail.com> for contributing the patch.
+! purifyArray now supports multidimensional arrays. Thanks
+ Sandro Miguel Marques <sandromiguel@sandromiguel.com> for contributing this patch.
+! initial and inherit settings available for width, height, and the min-/max-
+ versions thereof. Thanks Michael Kliewe <info@phpgansta.de> for contributing
+ this patch.
+! More color names are supported. Thanks Daijobou for contributing.
+- Compatibility fixes for PHP 7.3, including new CI for PHP 7.3
+ (thank you Lukas Neumann <lksnmnn@gmail.com>) and removal of
+ reserved words in our constants (thanks Darko Hrgovic <darko@darkodev.com>
+- Compatibility fixes for HHVM. Thanks Mateusz Turcza for contributing
+ this fix.
+- HTML Purifier now never defines __autoload, fixing #196. Thanks
+ Michael Kliewe for reporting.
+- In some situations, Config.php would report an undefined index: class
+ error; this has been fixed. Thanks DiLong Fa for contributing
+ this fix.
+- We no longer produce <script /> tags; we always explicitly write
+ out the open and close tag. Thanks Dimitri Gritsajuk
+ <gritsajuk.dimitri@gmail.com> for contributing this fix.
+- Better compatibility when IDNA constants are not present. Thanks
+ Mateusz Turcza <xemlock@gmail.com> for contributing this fix.
+
4.10.0, released 2018-02-22
# PHP 5.3 is no longer officially supported by HTML Purifier
(we did not specifically break support, but we are no longer
If you're using Composer to manage dependencies, you can use
- $ composer require "ezyang/htmlpurifier":"dev-master"
+ $ composer require ezyang/htmlpurifier
-4.10.0
\ No newline at end of file
+4.12.0
\ No newline at end of file
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run.
*
- * @version 4.10.0
+ * @version 4.12.0
*
* @warning
* You must *not* include any other HTML Purifier files before this file,
*/
/*
- HTML Purifier 4.10.0 - Standards Compliant HTML Filtering
+ HTML Purifier 4.12.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
* Version of HTML Purifier.
* @type string
*/
- public $version = '4.10.0';
+ public $version = '4.12.0';
/**
* Constant with version of HTML Purifier.
*/
- const VERSION = '4.10.0';
+ const VERSION = '4.12.0';
/**
* Global configuration object.
public function purifyArray($array_of_html, $config = null)
{
$context_array = array();
- foreach ($array_of_html as $key => $html) {
- $array_of_html[$key] = $this->purify($html, $config);
+ foreach($array_of_html as $key=>$value){
+ if (is_array($value)) {
+ $array[$key] = $this->purifyArray($value, $config);
+ } else {
+ $array[$key] = $this->purify($value, $config);
+ }
$context_array[$key] = $this->context;
}
$this->context = $context_array;
- return $array_of_html;
+ return $array;
}
/**
// PHP 5.3 and later support this functionality natively
if (function_exists('idn_to_ascii')) {
- $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
+ if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) {
+ $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
+ } else {
+ $string = idn_to_ascii($string);
+ }
// If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do,
* HTML Purifier's version
* @type string
*/
- public $version = '4.10.0';
+ public $version = '4.12.0';
/**
* Whether or not to automatically finalize
// zip(tail(trace), trace) -- but PHP is not Haskell har har
for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
// XXX this is not correct on some versions of HTML Purifier
- if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
+ if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
continue;
}
$frame = $trace[$i];
$doc = new DOMDocument();
$doc->encoding = 'UTF-8'; // theoretically, the above has this covered
+ $options = 0;
+ if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {
+ $options |= LIBXML_PARSEHUGE;
+ }
+
set_error_handler(array($this, 'muteErrorHandler'));
- $doc->loadHTML($html);
+ // loadHTML() fails on PHP 5.3 when second parameter is given
+ if ($options) {
+ $doc->loadHTML($html, $options);
+ } else {
+ $doc->loadHTML($html);
+ }
restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
*/
protected function getTagName($node)
{
- if (property_exists($node, 'tagName')) {
+ if (isset($node->tagName)) {
return $node->tagName;
- } else if (property_exists($node, 'nodeName')) {
+ } else if (isset($node->nodeName)) {
return $node->nodeName;
- } else if (property_exists($node, 'localName')) {
+ } else if (isset($node->localName)) {
return $node->localName;
}
return null;
*/
protected function getData($node)
{
- if (property_exists($node, 'data')) {
+ if (isset($node->data)) {
return $node->data;
- } else if (property_exists($node, 'nodeValue')) {
+ } else if (isset($node->nodeValue)) {
return $node->nodeValue;
- } else if (property_exists($node, 'textContent')) {
+ } else if (isset($node->textContent)) {
return $node->textContent;
}
return null;
}
if ($sourceMap) {
- $scss->setSourceMap(Compiler::SOURCE_MAP_FILE);
+ $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
}
if ($encoding) {
if ($root !== null) {
// If we have a root value which means we should rebuild.
- $out = array();
+ $out = [];
$out['root'] = $root;
$out['compiled'] = $this->compileFile($root);
$out['files'] = $this->scss->getParsedFiles();
include_once __DIR__ . '/src/Node.php';
include_once __DIR__ . '/src/Node/Number.php';
include_once __DIR__ . '/src/Parser.php';
- include_once __DIR__ . '/src/SourceMap/Base64VLQEncoder.php';
+ include_once __DIR__ . '/src/SourceMap/Base64.php';
+ include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
include_once __DIR__ . '/src/Type.php';
include_once __DIR__ . '/src/Util.php';
* @var array
*/
public $children;
+ /**
+ * @var \Leafo\ScssPhp\Block
+ */
+ public $selfParent;
}
protected $charsetSeen;
protected $sourceNames;
- private $indentLevel;
- private $commentsSeen;
- private $extends;
- private $extendsMap;
- private $parsedFiles;
- private $parser;
- private $sourceIndex;
- private $sourceLine;
- private $sourceColumn;
- private $stderr;
- private $shouldEvaluate;
- private $ignoreErrors;
+ protected $indentLevel;
+ protected $commentsSeen;
+ protected $extends;
+ protected $extendsMap;
+ protected $parsedFiles;
+ protected $parser;
+ protected $sourceIndex;
+ protected $sourceLine;
+ protected $sourceColumn;
+ protected $stderr;
+ protected $shouldEvaluate;
+ protected $ignoreErrors;
/**
* Constructor
$tempReplacement = $k > 0 ? array_slice($new, $k) : $new;
for ($l = count($tempReplacement) - 1; $l >= 0; $l--) {
- $slice = $tempReplacement[$l];
+ $slice = [];
+ foreach ($tempReplacement[$l] as $chunk) {
+ if (!in_array($chunk, $slice)) {
+ $slice[] = $chunk;
+ }
+ }
array_unshift($replacement, $slice);
if (! $this->isImmediateRelationshipCombinator(end($slice))) {
$wrapped->comments = [];
$wrapped->parent = $block;
$wrapped->children = $block->children;
+ $wrapped->selfParent = $block->selfParent;
$block->children = [[Type::T_BLOCK, $wrapped]];
}
*
* @return array
*/
- private function spliceTree($envs, Block $block, $without)
+ protected function spliceTree($envs, Block $block, $without)
{
$newBlock = null;
$newBlock = $b;
}
+ $newBlock->selfParent = $block->selfParent;
$type = isset($newBlock->type) ? $newBlock->type : Type::T_BLOCK;
return [$type, $newBlock];
*
* @return integer
*/
- private function compileWith($with)
+ protected function compileWith($with)
{
static $mapping = [
'rule' => self::WITH_RULE,
*
* @return \Leafo\ScssPhp\Compiler\Environment
*/
- private function filterWithout($envs, $without)
+ protected function filterWithout($envs, $without)
{
$filtered = [];
*
* @return boolean
*/
- private function isWithout($without, Block $block)
+ protected function isWithout($without, Block $block)
{
if ((($without & static::WITH_RULE) && isset($block->selectors)) ||
(($without & static::WITH_MEDIA) &&
$this->scope->children[] = $out;
if (count($block->children)) {
- $out->selectors = $this->multiplySelectors($env);
+ $out->selectors = $this->multiplySelectors($env, $block->selfParent);
$this->compileChildrenNoReturn($block->children, $out);
}
* @param array $stms
* @param \Leafo\ScssPhp\Formatter\OutputBlock $out
*
- * @return array
+ * @return array|null
*/
protected function compileChildren($stms, OutputBlock $out)
{
return $ret;
}
}
+ return null;
}
/**
switch ($child[0]) {
case Type::T_SCSSPHP_IMPORT_ONCE:
- list(, $rawPath) = $child;
-
- $rawPath = $this->reduce($rawPath);
+ $rawPath = $this->reduce($child[1]);
if (! $this->compileImport($rawPath, $out, true)) {
$out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
break;
case Type::T_IMPORT:
- list(, $rawPath) = $child;
-
- $rawPath = $this->reduce($rawPath);
+ $rawPath = $this->reduce($child[1]);
if (! $this->compileImport($rawPath, $out)) {
$out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
$isGlobal = in_array('!global', $flags);
if ($isGlobal) {
- $this->set($name[1], $this->reduce($value), false, $this->rootEnv);
+ $this->set($name[1], $this->reduce($value), false, $this->rootEnv, $value);
break;
}
|| $result === static::$null);
if (! $isDefault || $shouldSet) {
- $this->set($name[1], $this->reduce($value));
+ $this->set($name[1], $this->reduce($value), false, null, $value);
}
break;
}
// handle shorthand syntax: size / line-height
if ($compiledName === 'font') {
- if ($value[0] === Type::T_EXPRESSION && $value[1] === '/') {
- $value = $this->expToString($value);
- } elseif ($value[0] === Type::T_LIST) {
- foreach ($value[2] as &$item) {
+ if ($value[0] === Type::T_VARIABLE) {
+ // if the font value comes from variable, the content is already reduced (which means formulars where already calculated)
+ // so we need the original unreduced value
+ $value = $this->get($value[1], true, null, true);
+ }
+
+ $fontValue=&$value;
+ if ($value[0] === Type::T_LIST && $value[1]==',') {
+ // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
+ // we need to handle the first list element
+ $fontValue=&$value[2][0];
+ }
+
+ if ($fontValue[0] === Type::T_EXPRESSION && $fontValue[1] === '/') {
+ $fontValue = $this->expToString($fontValue);
+ } elseif ($fontValue[0] === Type::T_LIST) {
+ foreach ($fontValue[2] as &$item) {
if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
$item = $this->expToString($item);
}
break;
case Type::T_EXTEND:
- list(, $selectors) = $child;
-
- foreach ($selectors as $sel) {
+ foreach ($child[1] as $sel) {
$results = $this->evalSelectors([$sel]);
foreach ($results as $result) {
case Type::T_DEBUG:
list(, $value) = $child;
+ $fname = $this->sourceNames[$this->sourceIndex];
$line = $this->sourceLine;
$value = $this->compileValue($this->reduce($value, true));
- fwrite($this->stderr, "Line $line DEBUG: $value\n");
+ fwrite($this->stderr, "File $fname on line $line DEBUG: $value\n");
break;
case Type::T_WARN:
list(, $value) = $child;
+ $fname = $this->sourceNames[$this->sourceIndex];
$line = $this->sourceLine;
$value = $this->compileValue($this->reduce($value, true));
- fwrite($this->stderr, "Line $line WARN: $value\n");
+ fwrite($this->stderr, "File $fname on line $line WARN: $value\n");
break;
case Type::T_ERROR:
list(, $value) = $child;
+ $fname = $this->sourceNames[$this->sourceIndex];
$line = $this->sourceLine;
$value = $this->compileValue($this->reduce($value, true));
- $this->throwError("Line $line ERROR: $value\n");
+ $this->throwError("File $fname on line $line ERROR: $value\n");
break;
case Type::T_CONTROL:
switch ($value[0]) {
case Type::T_EXPRESSION:
if ($value[1] === '/') {
- return $this->shouldEval($value[2], $value[3]);
+ return $this->shouldEval($value[2]) || $this->shouldEval($value[3]);
}
// fall-thru
*/
protected function reduce($value, $inExp = false)
{
- list($type) = $value;
- switch ($type) {
+ switch ($value[0]) {
case Type::T_EXPRESSION:
list(, $op, $left, $right, $inParens) = $value;
return [Type::T_STRING, '', [$op, $exp]];
case Type::T_VARIABLE:
- list(, $name) = $value;
-
- return $this->reduce($this->get($name));
+ return $this->reduce($this->get($value[1]));
case Type::T_LIST:
foreach ($value[2] as &$item) {
return $value;
case Type::T_FUNCTION_CALL:
- list(, $name, $argValues) = $value;
-
- return $this->fncall($name, $argValues);
+ return $this->fncall($value[1], $value[2]);
default:
return $value;
*
* @return array|null
*/
- private function fncall($name, $argValues)
+ protected function fncall($name, $argValues)
{
// SCSS @function
if ($this->callScssFunction($name, $argValues, $returnValue)) {
public function normalizeValue($value)
{
$value = $this->coerceForExpression($this->reduce($value));
- list($type) = $value;
- switch ($type) {
+ switch ($value[0]) {
case Type::T_LIST:
$value = $this->extractInterpolation($value);
return $value;
case Type::T_STRING:
- return [$type, '"', [$this->compileStringContent($value)]];
+ return [$value[0], '"', [$this->compileStringContent($value)]];
case Type::T_NUMBER:
return $value->normalize();
* @param array $left
* @param array $right
*
- * @return array
+ * @return array|null
*/
protected function opAdd($left, $right)
{
return $strRight;
}
+ return null;
}
/**
* @param array $right
* @param boolean $shouldEval
*
- * @return array
+ * @return array|null
*/
protected function opAnd($left, $right, $shouldEval)
{
if (! $shouldEval) {
- return;
+ return null;
}
if ($left !== static::$false and $left !== static::$null) {
* @param array $right
* @param boolean $shouldEval
*
- * @return array
+ * @return array|null
*/
protected function opOr($left, $right, $shouldEval)
{
if (! $shouldEval) {
- return;
+ return null;
}
if ($left !== static::$false and $left !== static::$null) {
{
$value = $this->reduce($value);
- list($type) = $value;
-
- switch ($type) {
+ switch ($value[0]) {
case Type::T_KEYWORD:
return $value[1];
return $left . $this->compileValue($interpolate) . $right;
case Type::T_INTERPOLATE:
- // raw parse node
- list(, $exp) = $value;
-
// strip quotes if it's a string
- $reduced = $this->reduce($exp);
+ $reduced = $this->reduce($value[1]);
switch ($reduced[0]) {
case Type::T_LIST:
return 'null';
default:
- $this->throwError("unknown value type: $type");
+ $this->throwError("unknown value type: $value[0]");
}
}
* Find the final set of selectors
*
* @param \Leafo\ScssPhp\Compiler\Environment $env
+ * @param Leafo\ScssPhp\Block $selfParent
*
* @return array
*/
- protected function multiplySelectors(Environment $env)
+ protected function multiplySelectors(Environment $env, $selfParent = null)
{
$envs = $this->compactEnv($env);
$selectors = [];
$parentSelectors = [[]];
+ $selfParentSelectors = null;
+ if (!is_null($selfParent) and $selfParent->selectors) {
+ $selfParentSelectors = $this->evalSelectors($selfParent->selectors);
+ }
+
while ($env = array_pop($envs)) {
if (empty($env->selectors)) {
continue;
foreach ($env->selectors as $selector) {
foreach ($parentSelectors as $parent) {
- $selectors[] = $this->joinSelectors($parent, $selector);
+ $selectors[] = $this->joinSelectors($parent, $selector, $selfParentSelectors);
}
}
*
* @param array $parent
* @param array $child
- *
+ * @param array $selfParentSelectors
* @return array
*/
- protected function joinSelectors($parent, $child)
+ protected function joinSelectors($parent, $child, $selfParentSelectors = null)
{
$setSelf = false;
$out = [];
foreach ($part as $p) {
if ($p === static::$selfSelector) {
$setSelf = true;
-
- foreach ($parent as $i => $parentPart) {
+ if (is_null($selfParentSelectors)) {
+ $selfParentSelectors = $parent;
+ }
+ foreach ($selfParentSelectors as $i => $parentPart) {
if ($i > 0) {
$out[] = $newPart;
$newPart = [];
}
foreach ($parentPart as $pp) {
- $newPart[] = $pp;
+ $newPart[] = (is_array($pp) ? implode($pp) : $pp);
}
}
} else {
*
* @return array
*/
- private function compactEnv(Environment $env)
+ protected function compactEnv(Environment $env)
{
for ($envs = []; $env; $env = $env->parent) {
$envs[] = $env;
*
* @return \Leafo\ScssPhp\Compiler\Environment
*/
- private function extractEnv($envs)
+ protected function extractEnv($envs)
{
for ($env = null; $e = array_pop($envs);) {
$e->parent = $env;
* @param mixed $value
* @param boolean $shadow
* @param \Leafo\ScssPhp\Compiler\Environment $env
+ * @param mixed $valueUnreduced
*/
- protected function set($name, $value, $shadow = false, Environment $env = null)
+ protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
{
$name = $this->normalizeName($name);
}
if ($shadow) {
- $this->setRaw($name, $value, $env);
+ $this->setRaw($name, $value, $env, $valueUnreduced);
} else {
- $this->setExisting($name, $value, $env);
+ $this->setExisting($name, $value, $env, $valueUnreduced);
}
}
* @param string $name
* @param mixed $value
* @param \Leafo\ScssPhp\Compiler\Environment $env
+ * @param mixed $valueUnreduced
*/
- protected function setExisting($name, $value, Environment $env)
+ protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
{
$storeEnv = $env;
}
$env->store[$name] = $value;
+ if ($valueUnreduced) {
+ $env->storeUnreduced[$name] = $valueUnreduced;
+ }
}
/**
* @param string $name
* @param mixed $value
* @param \Leafo\ScssPhp\Compiler\Environment $env
+ * @param mixed $valueUnreduced
*/
- protected function setRaw($name, $value, Environment $env)
+ protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
{
$env->store[$name] = $value;
+ if ($valueUnreduced) {
+ $env->storeUnreduced[$name] = $valueUnreduced;
+ }
}
/**
* @param string $name
* @param boolean $shouldThrow
* @param \Leafo\ScssPhp\Compiler\Environment $env
+ * @param boolean $unreduced
*
- * @return mixed
+ * @return mixed|null
*/
- public function get($name, $shouldThrow = true, Environment $env = null)
+ public function get($name, $shouldThrow = true, Environment $env = null, $unreduced = false)
{
$normalizedName = $this->normalizeName($name);
$specialContentKey = static::$namespaces['special'] . 'content';
for (;;) {
if (array_key_exists($normalizedName, $env->store)) {
+ if ($unreduced && isset($env->storeUnreduced[$normalizedName])) {
+ return $env->storeUnreduced[$normalizedName];
+ }
return $env->store[$normalizedName];
}
}
// found nothing
+ return null;
}
/**
*
* @api
*
- * @param string $path
+ * @param string|callable $path
*/
public function addImportPath($path)
{
if (is_string($dir)) {
// check urls for normal import paths
foreach ($urls as $full) {
- $full = $dir
- . (! empty($dir) && substr($dir, -1) !== '/' ? '/' : '')
- . $full;
+ $separator = (
+ !empty($dir) &&
+ substr($dir, -1) !== '/' &&
+ substr($full, 0, 1) !== '/'
+ ) ? '/' : '';
+ $full = $dir . $separator . $full;
if ($this->fileExists($file = $full . '.scss') ||
($hasExtension && $this->fileExists($file = $full))
public function setIgnoreErrors($ignoreErrors)
{
$this->ignoreErrors = $ignoreErrors;
+ return $this;
}
/**
}
$line = $this->sourceLine;
- $msg = "$msg: line: $line";
+ $loc = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] . " on line $line" : "line: $line";
+ $msg = "$msg: $loc";
throw new CompilerException($msg);
}
foreach ($args as $arg) {
list($key, $value) = $arg;
- if ($key !== null) {
- $key = $key[1];
- }
+ $key = $key[1];
if (empty($key)) {
$posArgs[] = empty($arg[2]) ? $value : $arg;
*
* @return array|\Leafo\ScssPhp\Node\Number
*/
- private function coerceValue($value)
+ protected function coerceValue($value)
{
if (is_array($value) || $value instanceof \ArrayAccess) {
return $value;
$value = $this->coerceMap($value);
if ($value[0] !== Type::T_MAP) {
- $this->throwError('expecting map');
+ $this->throwError('expecting map, %s received', $value[0]);
}
return $value;
public function assertList($value)
{
if ($value[0] !== Type::T_LIST) {
- $this->throwError('expecting list');
+ $this->throwError('expecting list, %s received', $value[0]);
}
return $value;
return $color;
}
- $this->throwError('expecting color');
+ $this->throwError('expecting color, %s received', $value[0]);
}
/**
public function assertNumber($value)
{
if ($value[0] !== Type::T_NUMBER) {
- $this->throwError('expecting number');
+ $this->throwError('expecting number, %s received', $value[0]);
}
return $value[1];
*
* @return float
*/
- private function hueToRGB($m1, $m2, $h)
+ protected function hueToRGB($m1, $m2, $h)
{
if ($h < 0) {
$h += 1;
if (! isset($list[2][$n])) {
$this->throwError('Invalid argument for "n"');
- return;
+ return null;
}
$list[2][$n] = $args[2];
) {
$this->throwError('Invalid argument(s) for "comparable"');
- return;
+ return null;
}
$number1 = $number1->normalize();
if ($n < 1) {
$this->throwError("limit must be greater than or equal to 1");
- return;
+ return null;
}
return new Node\Number(mt_rand(1, $n), '');
*/
public $store;
+ /**
+ * @var array
+ */
+ public $storeUnreduced;
+
/**
* @var integer
*/
* the buffer position will be left at an invalid state. In order to
* avoid this, Compiler::seek() is used to remember and set buffer positions.
*
- * Before parsing a chain, use $s = $this->seek() to remember the current
+ * Before parsing a chain, use $s = $this->count to remember the current
* position into $s. Then if a chain fails, use $this->seek($s) to
* go back where we started.
*
*/
protected function parseChunk()
{
- $s = $this->seek();
+ $s = $this->count;
// the directives
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
- if ($this->literal('@at-root') &&
+ if ($this->literal('@at-root', 8) &&
($this->selectors($selector) || true) &&
($this->map($with) || true) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
$atRoot->selector = $selector;
$this->seek($s);
- if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) {
+ if ($this->literal('@media', 6) && $this->mediaQueryList($mediaQueryList) && $this->matchChar('{')) {
$media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
$media->queryList = $mediaQueryList[2];
$this->seek($s);
- if ($this->literal('@mixin') &&
+ if ($this->literal('@mixin', 6) &&
$this->keyword($mixinName) &&
($this->argumentDef($args) || true) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
$mixin->name = $mixinName;
$this->seek($s);
- if ($this->literal('@include') &&
+ if ($this->literal('@include', 8) &&
$this->keyword($mixinName) &&
- ($this->literal('(') &&
+ ($this->matchChar('(') &&
($this->argValues($argValues) || true) &&
- $this->literal(')') || true) &&
+ $this->matchChar(')') || true) &&
($this->end() ||
- $this->literal('{') && $hasBlock = true)
+ $this->matchChar('{') && $hasBlock = true)
) {
$child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
$this->seek($s);
- if ($this->literal('@scssphp-import-once') &&
+ if ($this->literal('@scssphp-import-once', 20) &&
$this->valueList($importPath) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@import') &&
+ if ($this->literal('@import', 7) &&
$this->valueList($importPath) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@import') &&
+ if ($this->literal('@import', 7) &&
$this->url($importPath) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@extend') &&
+ if ($this->literal('@extend', 7) &&
$this->selectors($selectors) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@function') &&
+ if ($this->literal('@function', 9) &&
$this->keyword($fnName) &&
$this->argumentDef($args) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
$func->name = $fnName;
$this->seek($s);
- if ($this->literal('@break') && $this->end()) {
+ if ($this->literal('@break', 6) && $this->end()) {
$this->append([Type::T_BREAK], $s);
return true;
$this->seek($s);
- if ($this->literal('@continue') && $this->end()) {
+ if ($this->literal('@continue', 9) && $this->end()) {
$this->append([Type::T_CONTINUE], $s);
return true;
$this->seek($s);
- if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) {
+ if ($this->literal('@return', 7) && ($this->valueList($retVal) || true) && $this->end()) {
$this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
return true;
$this->seek($s);
- if ($this->literal('@each') &&
+ if ($this->literal('@each', 5) &&
$this->genericList($varNames, 'variable', ',', false) &&
- $this->literal('in') &&
+ $this->literal('in', 2) &&
$this->valueList($list) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$each = $this->pushSpecialBlock(Type::T_EACH, $s);
$this->seek($s);
- if ($this->literal('@while') &&
+ if ($this->literal('@while', 6) &&
$this->expression($cond) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$while = $this->pushSpecialBlock(Type::T_WHILE, $s);
$while->cond = $cond;
$this->seek($s);
- if ($this->literal('@for') &&
+ if ($this->literal('@for', 4) &&
$this->variable($varName) &&
- $this->literal('from') &&
+ $this->literal('from', 4) &&
$this->expression($start) &&
- ($this->literal('through') ||
- ($forUntil = true && $this->literal('to'))) &&
+ ($this->literal('through', 7) ||
+ ($forUntil = true && $this->literal('to', 2))) &&
$this->expression($end) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
$for = $this->pushSpecialBlock(Type::T_FOR, $s);
$for->var = $varName[1];
$this->seek($s);
- if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) {
+ if ($this->literal('@if', 3) && $this->valueList($cond) && $this->matchChar('{')) {
$if = $this->pushSpecialBlock(Type::T_IF, $s);
$if->cond = $cond;
$if->cases = [];
$this->seek($s);
- if ($this->literal('@debug') &&
+ if ($this->literal('@debug', 6) &&
$this->valueList($value) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@warn') &&
+ if ($this->literal('@warn', 5) &&
$this->valueList($value) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@error') &&
+ if ($this->literal('@error', 6) &&
$this->valueList($value) &&
$this->end()
) {
$this->seek($s);
- if ($this->literal('@content') && $this->end()) {
+ if ($this->literal('@content', 8) && $this->end()) {
$this->append([Type::T_MIXIN_CONTENT], $s);
return true;
if (isset($last) && $last[0] === Type::T_IF) {
list(, $if) = $last;
- if ($this->literal('@else')) {
- if ($this->literal('{')) {
+ if ($this->literal('@else', 5)) {
+ if ($this->matchChar('{')) {
$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
- } elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) {
+ } elseif ($this->literal('if', 2) && $this->valueList($cond) && $this->matchChar('{')) {
$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
$else->cond = $cond;
}
}
// only retain the first @charset directive encountered
- if ($this->literal('@charset') &&
+ if ($this->literal('@charset', 8) &&
$this->valueList($charset) &&
$this->end()
) {
$this->seek($s);
// doesn't match built in directive, do generic one
- if ($this->literal('@', false) &&
+ if ($this->matchChar('@', false) &&
$this->keyword($dirName) &&
($this->variable($dirValue) || $this->openString('{', $dirValue) || true) &&
- $this->literal('{')
+ $this->matchChar('{')
) {
if ($dirName === 'media') {
$directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
// property shortcut
// captures most properties before having to parse a selector
if ($this->keyword($name, false) &&
- $this->literal(': ') &&
+ $this->literal(': ', 2) &&
$this->valueList($value) &&
$this->end()
) {
// variable assigns
if ($this->variable($name) &&
- $this->literal(':') &&
+ $this->matchChar(':') &&
$this->valueList($value) &&
$this->end()
) {
$this->seek($s);
// misc
- if ($this->literal('-->')) {
+ if ($this->literal('-->', 3)) {
return true;
}
// opening css block
- if ($this->selectors($selectors) && $this->literal('{')) {
+ if ($this->selectors($selectors) && $this->matchChar('{')) {
$this->pushBlock($selectors, $s);
return true;
$this->seek($s);
// property assign, or nested assign
- if ($this->propertyName($name) && $this->literal(':')) {
+ if ($this->propertyName($name) && $this->matchChar(':')) {
$foundSomething = false;
if ($this->valueList($value)) {
$foundSomething = true;
}
- if ($this->literal('{')) {
+ if ($this->matchChar('{')) {
$propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
$propBlock->prefix = $name;
$foundSomething = true;
$this->seek($s);
// closing a block
- if ($this->literal('}')) {
+ if ($this->matchChar('}')) {
$block = $this->popBlock();
if (isset($block->type) && $block->type === Type::T_INCLUDE) {
}
// extra stuff
- if ($this->literal(';') ||
- $this->literal('<!--')
+ if ($this->matchChar(';') ||
+ $this->literal('<!--', 4)
) {
return true;
}
$this->throwParseError('unexpected }');
}
+ if ($block->type == Type::T_AT_ROOT) {
+ // keeps the parent in case of self selector &
+ $block->selfParent = $block->parent;
+ }
+
$this->env = $block->parent;
unset($block->parent);
$comments = $block->comments;
- if (count($comments)) {
+ if ($comments) {
$this->env->comments = $comments;
unset($block->comments);
}
*
* @return integer
*/
- protected function seek($where = null)
+ protected function seek($where)
{
- if ($where === null) {
- return $this->count;
- }
-
$this->count = $where;
-
- return true;
}
/**
*/
protected function match($regex, &$out, $eatWhitespace = null)
{
- if (! isset($eatWhitespace)) {
- $eatWhitespace = $this->eatWhiteDefault;
- }
$r = '/' . $regex . '/' . $this->patternModifiers;
- if (preg_match($r, $this->buffer, $out, null, $this->count)) {
- $this->count += strlen($out[0]);
+ if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
+ return false;
+ }
- if ($eatWhitespace) {
- $this->whitespace();
- }
+ $this->count += strlen($out[0]);
- return true;
+ if (! isset($eatWhitespace)) {
+ $eatWhitespace = $this->eatWhiteDefault;
}
- return false;
+ if ($eatWhitespace) {
+ $this->whitespace();
+ }
+
+ return true;
}
+
/**
- * Match literal string
+ * Match a single string
*
- * @param string $what
+ * @param string $char
* @param boolean $eatWhitespace
*
* @return boolean
*/
- protected function literal($what, $eatWhitespace = null)
+ protected function matchChar($char, $eatWhitespace = null)
{
+
+ if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) {
+ return false;
+ }
+
+ $this->count++;
+
if (! isset($eatWhitespace)) {
$eatWhitespace = $this->eatWhiteDefault;
}
- $len = strlen($what);
+ if ($eatWhitespace) {
+ $this->whitespace();
+ }
+ return true;
+ }
- if (strcasecmp(substr($this->buffer, $this->count, $len), $what) === 0) {
- $this->count += $len;
- if ($eatWhitespace) {
- $this->whitespace();
- }
+ /**
+ * Match literal string
+ *
+ * @param string $what
+ * @param integer $len
+ * @param boolean $eatWhitespace
+ *
+ * @return boolean
+ */
+ protected function literal($what, $len, $eatWhitespace = null)
+ {
- return true;
+ if (strcasecmp(substr($this->buffer, $this->count, $len), $what) !== 0) {
+ return false;
}
- return false;
+ $this->count += $len;
+
+ if (! isset($eatWhitespace)) {
+ $eatWhitespace = $this->eatWhiteDefault;
+ }
+
+ if ($eatWhitespace) {
+ $this->whitespace();
+ }
+ return true;
}
+
/**
* Match some whitespace
*
$comments = $this->env->comments;
- if (count($comments)) {
+ if ($comments) {
$this->env->children = array_merge($this->env->children, $comments);
$this->env->comments = [];
}
$expressions = null;
$parts = [];
- if (($this->literal('only') && ($only = true) || $this->literal('not') && ($not = true) || true) &&
+ if (($this->literal('only', 4) && ($only = true) || $this->literal('not', 3) && ($not = true) || true) &&
$this->mixedKeyword($mediaType)
) {
$prop = [Type::T_MEDIA_TYPE];
$parts[] = $prop;
}
- if (empty($parts) || $this->literal('and')) {
+ if (empty($parts) || $this->literal('and', 3)) {
$this->genericList($expressions, 'mediaExpression', 'and', false);
if (is_array($expressions)) {
*/
protected function mediaExpression(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
$value = null;
- if ($this->literal('(') &&
+ if ($this->matchChar('(') &&
$this->expression($feature) &&
- ($this->literal(':') && $this->expression($value) || true) &&
- $this->literal(')')
+ ($this->matchChar(':') && $this->expression($value) || true) &&
+ $this->matchChar(')')
) {
$out = [Type::T_MEDIA_EXPRESSION, $feature];
*/
protected function argValue(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
$keyword = null;
- if (! $this->variable($keyword) || ! $this->literal(':')) {
+ if (! $this->variable($keyword) || ! $this->matchChar(':')) {
$this->seek($s);
$keyword = null;
}
if ($this->genericList($value, 'expression')) {
$out = [$keyword, $value, false];
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('...')) {
+ if ($this->literal('...', 3)) {
$out[2] = true;
} else {
$this->seek($s);
*/
protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
{
- $s = $this->seek();
+ $s = $this->count;
$items = [];
-
+ $value = null;
while ($this->$parseItem($value)) {
$items[] = $value;
if ($delim) {
- if (! $this->literal($delim)) {
+ if (! $this->literal($delim, strlen($delim))) {
break;
}
}
}
- if (count($items) === 0) {
+ if (!$items) {
$this->seek($s);
return false;
*/
protected function expression(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('(')) {
- if ($this->literal(')')) {
+ if ($this->matchChar('(')) {
+ if ($this->matchChar(')')) {
$out = [Type::T_LIST, '', []];
return true;
}
- if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) {
+ if ($this->valueList($out) && $this->matchChar(')') && $out[0] === Type::T_LIST) {
return true;
}
{
$operators = static::$operatorPattern;
- $ss = $this->seek();
+ $ss = $this->count;
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
ctype_space($this->buffer[$this->count - 1]);
}
$lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
- $ss = $this->seek();
+ $ss = $this->count;
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
ctype_space($this->buffer[$this->count - 1]);
}
*/
protected function value(&$out)
{
- $s = $this->seek();
- if ($this->literal('url(') && $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)) {
+ if (! isset($this->buffer[$this->count])) {
+ return false;
+ }
+
+ $s = $this->count;
+ $char = $this->buffer[$this->count];
+
+ if ($this->literal('url(', 4) && $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)) {
$len = strspn($this->buffer, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=', $this->count);
$this->count += $len;
- if ($this->literal(')')) {
+ if ($this->matchChar(')')) {
$content = substr($this->buffer, $s, $this->count - $s);
$out = [Type::T_KEYWORD, $content];
$this->seek($s);
- if ($this->literal('not', false) && $this->whitespace() && $this->value($inner)) {
- $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
+ // not
+ if ($char === 'n' && $this->literal('not', 3, false)) {
+ if ($this->whitespace() && $this->value($inner)) {
+ $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
+ return true;
+ }
- return true;
- }
+ $this->seek($s);
- $this->seek($s);
+ if ($this->parenValue($inner)) {
+ $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
+ return true;
+ }
- if ($this->literal('not', false) && $this->parenValue($inner)) {
- $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
+ $this->seek($s);
+ }
- return true;
+ // addition
+ if ($char === '+') {
+ $this->count++;
+ if ($this->value($inner)) {
+ $out = [Type::T_UNARY, '+', $inner, $this->inParens];
+ return true;
+ }
+ $this->count--;
+ return false;
}
- $this->seek($s);
- if ($this->literal('+') && $this->value($inner)) {
- $out = [Type::T_UNARY, '+', $inner, $this->inParens];
+ // negation
+ if ($char === '-') {
+ $this->count++;
+ if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) {
+ $out = [Type::T_UNARY, '-', $inner, $this->inParens];
+ return true;
+ }
+ $this->count--;
+ }
+ // paren
+ if ($char === '(' && $this->parenValue($out)) {
return true;
}
- $this->seek($s);
+ if ($char === '#') {
+ if ($this->interpolation($out) || $this->color($out)) {
+ return true;
+ }
+ }
- // negation
- if ($this->literal('-', false) &&
- ($this->variable($inner) ||
- $this->unit($inner) ||
- $this->parenValue($inner))
- ) {
- $out = [Type::T_UNARY, '-', $inner, $this->inParens];
+ if ($char === '$' && $this->variable($out)) {
+ return true;
+ }
+ if ($char === 'p' && $this->progid($out)) {
return true;
}
- $this->seek($s);
+ if (($char === '"' || $char === "'") && $this->string($out)) {
+ return true;
+ }
- if ($this->parenValue($out) ||
- $this->interpolation($out) ||
- $this->variable($out) ||
- $this->color($out) ||
- $this->unit($out) ||
- $this->string($out) ||
- $this->func($out) ||
- $this->progid($out)
- ) {
+
+ if ($this->unit($out)) {
return true;
}
- if ($this->keyword($keyword)) {
+ if ($this->keyword($keyword, false)) {
+ if ($this->func($keyword, $out)) {
+ return true;
+ }
+
+ $this->whitespace();
+
if ($keyword === 'null') {
$out = [Type::T_NULL];
} else {
*/
protected function parenValue(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
$inParens = $this->inParens;
- if ($this->literal('(')) {
- if ($this->literal(')')) {
+ if ($this->matchChar('(')) {
+ if ($this->matchChar(')')) {
$out = [Type::T_LIST, '', []];
return true;
$this->inParens = true;
- if ($this->expression($exp) && $this->literal(')')) {
+ if ($this->expression($exp) && $this->matchChar(')')) {
$out = $exp;
$this->inParens = $inParens;
*/
protected function progid(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('progid:', false) &&
+ if ($this->literal('progid:', 7, false) &&
$this->openString('(', $fn) &&
- $this->literal('(')
+ $this->matchChar('(')
) {
$this->openString(')', $args, '(');
- if ($this->literal(')')) {
+ if ($this->matchChar(')')) {
$out = [Type::T_STRING, '', [
'progid:', $fn, '(', $args, ')'
]];
/**
* Parse function call
*
+ * @param string $name
* @param array $out
*
* @return boolean
*/
- protected function func(&$func)
+ protected function func($name, &$func)
{
- $s = $this->seek();
+ $s = $this->count;
- if ($this->keyword($name, false) &&
- $this->literal('(')
- ) {
+ if ($this->matchChar('(')) {
if ($name === 'alpha' && $this->argumentList($args)) {
$func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
}
if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
- $ss = $this->seek();
+ $ss = $this->count;
- if ($this->argValues($args) && $this->literal(')')) {
+ if ($this->argValues($args) && $this->matchChar(')')) {
$func = [Type::T_FUNCTION_CALL, $name, $args];
return true;
}
if (($this->openString(')', $str, '(') || true) &&
- $this->literal(')')
+ $this->matchChar(')')
) {
$args = [];
*/
protected function argumentList(&$out)
{
- $s = $this->seek();
- $this->literal('(');
+ $s = $this->count;
+ $this->matchChar('(');
$args = [];
while ($this->keyword($var)) {
- if ($this->literal('=') && $this->expression($exp)) {
+ if ($this->matchChar('=') && $this->expression($exp)) {
$args[] = [Type::T_STRING, '', [$var . '=']];
$arg = $exp;
} else {
$args[] = $arg;
- if (! $this->literal(',')) {
+ if (! $this->matchChar(',')) {
break;
}
$args[] = [Type::T_STRING, '', [', ']];
}
- if (! $this->literal(')') || ! count($args)) {
+ if (! $this->matchChar(')') || !$args) {
$this->seek($s);
return false;
*/
protected function argumentDef(&$out)
{
- $s = $this->seek();
- $this->literal('(');
+ $s = $this->count;
+ $this->matchChar('(');
$args = [];
while ($this->variable($var)) {
$arg = [$var[1], null, false];
- $ss = $this->seek();
+ $ss = $this->count;
- if ($this->literal(':') && $this->genericList($defaultVal, 'expression')) {
+ if ($this->matchChar(':') && $this->genericList($defaultVal, 'expression')) {
$arg[1] = $defaultVal;
} else {
$this->seek($ss);
}
- $ss = $this->seek();
+ $ss = $this->count;
- if ($this->literal('...')) {
- $sss = $this->seek();
+ if ($this->literal('...', 3)) {
+ $sss = $this->count;
- if (! $this->literal(')')) {
+ if (! $this->matchChar(')')) {
$this->throwParseError('... has to be after the final argument');
}
$args[] = $arg;
- if (! $this->literal(',')) {
+ if (! $this->matchChar(',')) {
break;
}
}
- if (! $this->literal(')')) {
+ if (! $this->matchChar(')')) {
$this->seek($s);
return false;
*/
protected function map(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
- if (! $this->literal('(')) {
+ if (! $this->matchChar('(')) {
return false;
}
$keys = [];
$values = [];
- while ($this->genericList($key, 'expression') && $this->literal(':') &&
+ while ($this->genericList($key, 'expression') && $this->matchChar(':') &&
$this->genericList($value, 'expression')
) {
$keys[] = $key;
$values[] = $value;
- if (! $this->literal(',')) {
+ if (! $this->matchChar(',')) {
break;
}
}
- if (! count($keys) || ! $this->literal(')')) {
+ if (!$keys || ! $this->matchChar(')')) {
$this->seek($s);
return false;
protected function color(&$out)
{
$color = [Type::T_COLOR];
+ $s = $this->count;
+
+ if ($this->match('(#([0-9a-f]+))', $m)) {
+ $nofValues = strlen($m[2]);
+ $hasAlpha = $nofValues === 4 || $nofValues === 8;
+ $channels = $hasAlpha ? [4, 3, 2, 1] : [3, 2, 1];
+
+ switch ($nofValues) {
+ case 3:
+ case 4:
+ $num = hexdec($m[2]);
+
+ foreach ($channels as $i) {
+ $t = $num & 0xf;
+ $color[$i] = $t << 4 | $t;
+ $num >>= 4;
+ }
+ break;
- if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
- if (isset($m[3])) {
- $num = hexdec($m[3]);
+ case 6:
+ case 8:
+ $num = hexdec($m[2]);
- foreach ([3, 2, 1] as $i) {
- $t = $num & 0xf;
- $color[$i] = $t << 4 | $t;
- $num >>= 4;
- }
- } else {
- $num = hexdec($m[2]);
+ foreach ($channels as $i) {
+ $color[$i] = $num & 0xff;
+ $num >>= 8;
+ }
+ break;
+
+ default:
+ $this->seek($s);
+
+ return false;
+ }
- foreach ([3, 2, 1] as $i) {
- $color[$i] = $num & 0xff;
- $num >>= 8;
+ if ($hasAlpha) {
+ if ($color[4] === 255) {
+ $color[4] = 1; // fully opaque
+ } else {
+ $color[4] = round($color[4] / 255, 3);
}
}
*/
protected function string(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('"', false)) {
+ if ($this->matchChar('"', false)) {
$delim = '"';
- } elseif ($this->literal("'", false)) {
+ } elseif ($this->matchChar("'", false)) {
$delim = "'";
} else {
return false;
$content[] = '#{'; // ignore it
}
} elseif ($m[2] === '\\') {
- if ($this->literal('"', false)) {
+ if ($this->matchChar('"', false)) {
$content[] = $m[2] . '"';
- } elseif ($this->literal("'", false)) {
+ } elseif ($this->matchChar("'", false)) {
$content[] = $m[2] . "'";
+ } elseif ($this->literal("\\", 1, false)) {
+ $content[] = $m[2] . "\\";
} else {
$content[] = $m[2];
}
$this->eatWhiteDefault = $oldWhite;
- if ($this->literal($delim)) {
+ if ($this->literal($delim, strlen($delim))) {
if ($hasInterpolation) {
$delim = '"';
foreach ($content as &$string) {
- if ($string === "\\'") {
+ if ($string === "\\\\") {
+ $string = "\\";
+ } elseif ($string === "\\'") {
$string = "'";
} elseif ($string === '\\"') {
$string = '"';
$this->eatWhiteDefault = $oldWhite;
- if (count($parts) === 0) {
+ if (!$parts) {
return false;
}
$this->eatWhiteDefault = $oldWhite;
- if (count($content) === 0) {
+ if (!$content) {
return false;
}
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = true;
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('#{') && $this->valueList($value) && $this->literal('}', false)) {
+ if ($this->literal('#{', 2) && $this->valueList($value) && $this->matchChar('}', false)) {
if ($lookWhite) {
$left = preg_match('/\s/', $this->buffer[$s - 1]) ? ' ' : '';
$right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
return true;
}
+ $this->seek($s);
+
+ if ($this->literal('#{', 2) && $this->selectorSingle($sel) && $this->matchChar('}', false)) {
+ $out = $sel[0];
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if ($this->eatWhiteDefault) {
+ $this->whitespace();
+ }
+
+ return true;
+ }
+
$this->seek($s);
$this->eatWhiteDefault = $oldWhite;
continue;
}
- if (count($parts) === 0 && $this->match('[:.#]', $m, false)) {
+ if (!$parts && $this->match('[:.#]', $m, false)) {
// css hacks
$parts[] = $m[0];
continue;
$this->eatWhiteDefault = $oldWhite;
- if (count($parts) === 0) {
+ if (!$parts) {
return false;
}
*/
protected function selectors(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
$selectors = [];
while ($this->selector($sel)) {
$selectors[] = $sel;
- if (! $this->literal(',')) {
+ if (! $this->matchChar(',')) {
break;
}
- while ($this->literal(',')) {
+ while ($this->matchChar(',')) {
; // ignore extra
}
}
- if (count($selectors) === 0) {
+ if (!$selectors) {
$this->seek($s);
return false;
break;
}
- if (count($selector) === 0) {
+ if (!$selector) {
return false;
}
$parts = [];
- if ($this->literal('*', false)) {
+ if ($this->matchChar('*', false)) {
$parts[] = '*';
}
for (;;) {
- // see if we can stop early
- if ($this->match('\s*[{,]', $m)) {
- $this->count--;
+ if (! isset($this->buffer[$this->count])) {
break;
}
- $s = $this->seek();
+ $s = $this->count;
+ $char = $this->buffer[$this->count];
- // self
- if ($this->literal('&', false)) {
- $parts[] = Compiler::$selfSelector;
- continue;
+ // see if we can stop early
+ if ($char === '{' || $char === ',' || $char === ';' || $char === '}' || $char === '@') {
+ break;
}
- if ($this->literal('.', false)) {
- $parts[] = '.';
- continue;
- }
- if ($this->literal('|', false)) {
- $parts[] = '|';
- continue;
+ //self
+ switch ($char) {
+ case '&':
+ $parts[] = Compiler::$selfSelector;
+ $this->count++;
+ continue 2;
+ case '.':
+ $parts[] = '.';
+ $this->count++;
+ continue 2;
+ case '|':
+ $parts[] = '|';
+ $this->count++;
+ continue 2;
}
- if ($this->match('\\\\\S', $m)) {
- $parts[] = $m[0];
- continue;
- }
- // for keyframes
- if ($this->unit($unit)) {
- $parts[] = $unit;
+ if ($char === '\\' && $this->match('\\\\\S', $m)) {
+ $parts[] = $m[0];
continue;
}
- if ($this->keyword($name)) {
- $parts[] = $name;
- continue;
- }
- if ($this->interpolation($inter)) {
- $parts[] = $inter;
- continue;
+ if ($char === '%') {
+ $this->count++;
+ if ($this->placeholder($placeholder)) {
+ $parts[] = '%';
+ $parts[] = $placeholder;
+ continue;
+ }
+ break;
}
- if ($this->literal('%', false) && $this->placeholder($placeholder)) {
- $parts[] = '%';
- $parts[] = $placeholder;
- continue;
- }
+ if ($char === '#') {
+ if ($this->interpolation($inter)) {
+ $parts[] = $inter;
+ continue;
+ }
- if ($this->literal('#', false)) {
$parts[] = '#';
+ $this->count++;
continue;
}
- // a pseudo selector
- if ($this->match('::?', $m) && $this->mixedKeyword($nameParts)) {
- $parts[] = $m[0];
- foreach ($nameParts as $sub) {
- $parts[] = $sub;
+ // a pseudo selector
+ if ($char === ':') {
+ if ($this->buffer[$this->count + 1] === ':') {
+ $this->count += 2;
+ $part = '::';
+ } else {
+ $this->count++;
+ $part = ':';
}
+ if ($this->mixedKeyword($nameParts)) {
+ $parts[] = $part;
+
+ foreach ($nameParts as $sub) {
+ $parts[] = $sub;
+ }
+
+ $ss = $this->count;
- $ss = $this->seek();
+ if ($this->matchChar('(') &&
+ ($this->openString(')', $str, '(') || true) &&
+ $this->matchChar(')')
+ ) {
+ $parts[] = '(';
- if ($this->literal('(') &&
- ($this->openString(')', $str, '(') || true) &&
- $this->literal(')')
- ) {
- $parts[] = '(';
+ if (! empty($str)) {
+ $parts[] = $str;
+ }
- if (! empty($str)) {
- $parts[] = $str;
+ $parts[] = ')';
+ } else {
+ $this->seek($ss);
}
- $parts[] = ')';
- } else {
- $this->seek($ss);
+ continue;
}
-
- continue;
}
+
$this->seek($s);
+
// attribute selector
- if ($this->literal('[') &&
- ($this->openString(']', $str, '[') || true) &&
- $this->literal(']')
+ if ($char === '[' &&
+ $this->matchChar('[') &&
+ ($this->openString(']', $str, '[') || true) &&
+ $this->matchChar(']')
) {
$parts[] = '[';
$this->seek($s);
+
+ // for keyframes
+ if ($this->unit($unit)) {
+ $parts[] = $unit;
+ continue;
+ }
+
+ if ($this->keyword($name)) {
+ $parts[] = $name;
+ continue;
+ }
+
+
+
+
break;
}
$this->eatWhiteDefault = $oldWhite;
- if (count($parts) === 0) {
+ if (!$parts) {
return false;
}
*/
protected function variable(&$out)
{
- $s = $this->seek();
+ $s = $this->count;
- if ($this->literal('$', false) && $this->keyword($name)) {
+ if ($this->matchChar('$', false) && $this->keyword($name)) {
$out = [Type::T_VARIABLE, $name];
return true;
*/
protected function end()
{
- if ($this->literal(';')) {
+ if ($this->matchChar(';')) {
return true;
}
--- /dev/null
+<?php
+/**
+ * SCSSPHP
+ *
+ * @copyright 2012-2015 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://leafo.github.io/scssphp
+ */
+
+namespace Leafo\ScssPhp\SourceMap;
+
+/**
+ * Base 64 Encode/Decode
+ *
+ * @author Anthon Pang <anthon.pang@gmail.com>
+ */
+class Base64
+{
+ /**
+ * @var array
+ */
+ private static $encodingMap = [
+ 0 => 'A',
+ 1 => 'B',
+ 2 => 'C',
+ 3 => 'D',
+ 4 => 'E',
+ 5 => 'F',
+ 6 => 'G',
+ 7 => 'H',
+ 8 => 'I',
+ 9 => 'J',
+ 10 => 'K',
+ 11 => 'L',
+ 12 => 'M',
+ 13 => 'N',
+ 14 => 'O',
+ 15 => 'P',
+ 16 => 'Q',
+ 17 => 'R',
+ 18 => 'S',
+ 19 => 'T',
+ 20 => 'U',
+ 21 => 'V',
+ 22 => 'W',
+ 23 => 'X',
+ 24 => 'Y',
+ 25 => 'Z',
+ 26 => 'a',
+ 27 => 'b',
+ 28 => 'c',
+ 29 => 'd',
+ 30 => 'e',
+ 31 => 'f',
+ 32 => 'g',
+ 33 => 'h',
+ 34 => 'i',
+ 35 => 'j',
+ 36 => 'k',
+ 37 => 'l',
+ 38 => 'm',
+ 39 => 'n',
+ 40 => 'o',
+ 41 => 'p',
+ 42 => 'q',
+ 43 => 'r',
+ 44 => 's',
+ 45 => 't',
+ 46 => 'u',
+ 47 => 'v',
+ 48 => 'w',
+ 49 => 'x',
+ 50 => 'y',
+ 51 => 'z',
+ 52 => '0',
+ 53 => '1',
+ 54 => '2',
+ 55 => '3',
+ 56 => '4',
+ 57 => '5',
+ 58 => '6',
+ 59 => '7',
+ 60 => '8',
+ 61 => '9',
+ 62 => '+',
+ 63 => '/',
+ ];
+
+ /**
+ * @var array
+ */
+ private static $decodingMap = [
+ 'A' => 0,
+ 'B' => 1,
+ 'C' => 2,
+ 'D' => 3,
+ 'E' => 4,
+ 'F' => 5,
+ 'G' => 6,
+ 'H' => 7,
+ 'I' => 8,
+ 'J' => 9,
+ 'K' => 10,
+ 'L' => 11,
+ 'M' => 12,
+ 'N' => 13,
+ 'O' => 14,
+ 'P' => 15,
+ 'Q' => 16,
+ 'R' => 17,
+ 'S' => 18,
+ 'T' => 19,
+ 'U' => 20,
+ 'V' => 21,
+ 'W' => 22,
+ 'X' => 23,
+ 'Y' => 24,
+ 'Z' => 25,
+ 'a' => 26,
+ 'b' => 27,
+ 'c' => 28,
+ 'd' => 29,
+ 'e' => 30,
+ 'f' => 31,
+ 'g' => 32,
+ 'h' => 33,
+ 'i' => 34,
+ 'j' => 35,
+ 'k' => 36,
+ 'l' => 37,
+ 'm' => 38,
+ 'n' => 39,
+ 'o' => 40,
+ 'p' => 41,
+ 'q' => 42,
+ 'r' => 43,
+ 's' => 44,
+ 't' => 45,
+ 'u' => 46,
+ 'v' => 47,
+ 'w' => 48,
+ 'x' => 49,
+ 'y' => 50,
+ 'z' => 51,
+ 0 => 52,
+ 1 => 53,
+ 2 => 54,
+ 3 => 55,
+ 4 => 56,
+ 5 => 57,
+ 6 => 58,
+ 7 => 59,
+ 8 => 60,
+ 9 => 61,
+ '+' => 62,
+ '/' => 63,
+ ];
+
+ /**
+ * Convert to base64
+ *
+ * @param integer $value
+ *
+ * @return string
+ */
+ public static function encode($value)
+ {
+ return self::$encodingMap[$value];
+ }
+
+ /**
+ * Convert from base64
+ *
+ * @param string $value
+ *
+ * @return integer
+ */
+ public static function decode($value)
+ {
+ return self::$decodingMap[$value];
+ }
+}
--- /dev/null
+<?php
+/**
+ * SCSSPHP
+ *
+ * @copyright 2012-2015 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://leafo.github.io/scssphp
+ */
+
+namespace Leafo\ScssPhp\SourceMap;
+
+use Leafo\ScssPhp\SourceMap\Base64;
+
+/**
+ * Base 64 VLQ
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://github.com/google/closure-compiler/blob/master/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author John Lenz <johnlenz@google.com>
+ * @author Anthon Pang <anthon.pang@gmail.com>
+ */
+class Base64VLQ
+{
+ // A Base64 VLQ digit can represent 5 bits, so it is base-32.
+ const VLQ_BASE_SHIFT = 5;
+
+ // A mask of bits for a VLQ digit (11111), 31 decimal.
+ const VLQ_BASE_MASK = 31;
+
+ // The continuation bit is the 6th bit.
+ const VLQ_CONTINUATION_BIT = 32;
+
+ /**
+ * Returns the VLQ encoded value.
+ *
+ * @param integer $value
+ *
+ * @return string
+ */
+ public static function encode($value)
+ {
+ $encoded = '';
+ $vlq = self::toVLQSigned($value);
+
+ do {
+ $digit = $vlq & self::VLQ_BASE_MASK;
+ $vlq >>= self::VLQ_BASE_SHIFT;
+
+ if ($vlq > 0) {
+ $digit |= self::VLQ_CONTINUATION_BIT;
+ }
+
+ $encoded .= Base64::encode($digit);
+ } while ($vlq > 0);
+
+ return $encoded;
+ }
+
+ /**
+ * Decodes VLQValue.
+ *
+ * @param string $str
+ * @param integer $index
+ *
+ * @return integer
+ */
+ public static function decode($str, &$index)
+ {
+ $result = 0;
+ $shift = 0;
+
+ do {
+ $c = $str[$index++];
+ $digit = Base64::decode($c);
+ $continuation = ($digit & self::VLQ_CONTINUATION_BIT) != 0;
+ $digit &= self::VLQ_BASE_MASK;
+ $result = $result + ($digit << $shift);
+ $shift = $shift + self::VLQ_BASE_SHIFT;
+ } while ($continuation);
+
+ return self::fromVLQSigned($result);
+ }
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ *
+ * @param integer $value
+ *
+ * @return integer
+ */
+ private static function toVLQSigned($value)
+ {
+ if ($value < 0) {
+ return ((-$value) << 1) + 1;
+ }
+
+ return ($value << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ *
+ * @param integer $value
+ *
+ * @return integer
+ */
+ private static function fromVLQSigned($value)
+ {
+ $negate = ($value & 1) === 1;
+ $value = $value >> 1;
+
+ return $negate ? -$value : $value;
+ }
+}
*
* @var array
*/
- private $charToIntMap = array(
+ private $charToIntMap = [
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7,
'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15,
'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23,
'o' => 40, 'p' => 41, 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47,
'w' => 48, 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55,
4 => 56, 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
- );
+ ];
/**
* Integer to char map
*
* @var array
*/
- private $intToCharMap = array(
+ private $intToCharMap = [
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H',
8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P',
16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X',
40 => 'o', 41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v',
48 => 'w', 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', 63 => '/',
- );
+ ];
/**
* Constructor
*
* @var array
*/
- protected $defaultOptions = array(
+ protected $defaultOptions = [
// an optional source root, useful for relocating source files
// on a server or removing repeated values in the 'sources' entry.
// This value is prepended to the individual entries in the 'source' field.
// base path for filename normalization
'sourceMapBasepath' => ''
- );
+ ];
/**
* The base64 VLQ encoder
*
- * @var \Leafo\ScssPhp\SourceMap\Base64VLQEncoder
+ * @var \Leafo\ScssPhp\SourceMap\Base64VLQ
*/
protected $encoder;
*
* @var array
*/
- protected $mappings = array();
+ protected $mappings = [];
/**
* Array of contents map
*
* @var array
*/
- protected $contentsMap = array();
+ protected $contentsMap = [];
/**
* File to content map
*
* @var array
*/
- protected $sources = array();
- protected $source_keys = array();
+ protected $sources = [];
+ protected $source_keys = [];
/**
* @var array
public function __construct(array $options = [])
{
$this->options = array_merge($this->defaultOptions, $options);
- $this->encoder = new Base64VLQEncoder();
+ $this->encoder = new Base64VLQ();
}
/**
*/
public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
{
- $this->mappings[] = array(
+ $this->mappings[] = [
'generated_line' => $generatedLine,
'generated_column' => $generatedColumn,
'original_line' => $originalLine,
'original_column' => $originalColumn,
'source_file' => $sourceFile
- );
+ ];
$this->sources[$sourceFile] = $sourceFile;
}
*/
public function generateJson()
{
- $sourceMap = array();
+ $sourceMap = [];
$mappings = $this->generateMappings();
// File version (always the first entry in the object) and must be a positive integer.
}
// A list of original sources used by the 'mappings' entry.
- $sourceMap['sources'] = array();
+ $sourceMap['sources'] = [];
foreach ($this->sources as $source_uri => $source_filename) {
$sourceMap['sources'][] = $this->normalizeFilename($source_filename);
}
// A list of symbol names used by the 'mappings' entry.
- $sourceMap['names'] = array();
+ $sourceMap['names'] = [];
// A string with the encoded mapping data.
$sourceMap['mappings'] = $mappings;
unset($sourceMap['sourceRoot']);
}
- return json_encode($sourceMap);
+ return json_encode($sourceMap, JSON_UNESCAPED_SLASHES);
}
/**
return null;
}
- $content = array();
+ $content = [];
foreach ($this->sources as $sourceFile) {
$content[] = file_get_contents($sourceFile);
$this->source_keys = array_flip(array_keys($this->sources));
// group mappings by generated line number.
- $groupedMap = $groupedMapEncoded = array();
+ $groupedMap = $groupedMapEncoded = [];
foreach ($this->mappings as $m) {
$groupedMap[$m['generated_line']][] = $m;
$groupedMapEncoded[] = ';';
}
- $lineMapEncoded = array();
+ $lineMapEncoded = [];
$lastGeneratedColumn = 0;
foreach ($line_map as $m) {
$basePath = $this->options['sourceMapBasepath'];
// "Trim" the 'sourceMapBasepath' from the output filename.
- if (strpos($filename, $basePath) === 0) {
+ if (strlen($basePath) && strpos($filename, $basePath) === 0) {
$filename = substr($filename, strlen($basePath));
}
*/
public static function encodeURIComponent($string)
{
- $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
+ $revert = ['%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'];
return strtr(rawurlencode($string), $revert);
}
*/
class Version
{
- const VERSION = 'v0.7.6';
+ const VERSION = 'v0.7.8';
}
{
/**
* @param string $expectedValue
- * @param Token $foundToken
*
* @return self
*/
private $value;
/**
- * @param NodeInterface $selector
- * @param string $namespace
- * @param string $attribute
- * @param string $operator
- * @param string $value
+ * @param string $namespace
+ * @param string $attribute
+ * @param string $operator
+ * @param string $value
*/
public function __construct(NodeInterface $selector, $namespace, $attribute, $operator, $value)
{
private $name;
/**
- * @param NodeInterface $selector
- * @param string $name
+ * @param string $name
*/
public function __construct(NodeInterface $selector, $name)
{
private $subSelector;
/**
- * @param NodeInterface $selector
- * @param string $combinator
- * @param NodeInterface $subSelector
+ * @param string $combinator
*/
public function __construct(NodeInterface $selector, $combinator, NodeInterface $subSelector)
{
private $arguments;
/**
- * @param NodeInterface $selector
- * @param string $name
- * @param Token[] $arguments
+ * @param string $name
+ * @param Token[] $arguments
*/
public function __construct(NodeInterface $selector, $name, array $arguments = [])
{
private $id;
/**
- * @param NodeInterface $selector
- * @param string $id
+ * @param string $id
*/
public function __construct(NodeInterface $selector, $id)
{
private $identifier;
/**
- * @param NodeInterface $selector
- * @param string $identifier
+ * @param string $identifier
*/
public function __construct(NodeInterface $selector, $identifier)
{
private $pseudoElement;
/**
- * @param NodeInterface $tree
- * @param string|null $pseudoElement
+ * @param string|null $pseudoElement
*/
public function __construct(NodeInterface $tree, $pseudoElement = null)
{
/**
* Parses next simple node (hash, class, pseudo, negation).
*
- * @param TokenStream $stream
- * @param bool $insideNegation
+ * @param bool $insideNegation
*
* @return array
*
}
if ($next->isDelimiter(['*'])) {
- return;
+ return null;
}
throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next);
if (0x10000 > $c) {
return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
}
+
+ return '';
}, $value);
}
}
$this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1'));
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ParseException
- * @expectedExceptionMessage Expected identifier, but <eof at 3> found.
- */
public function testParseExceptions()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ParseException');
+ $this->expectExceptionMessage('Expected identifier, but <eof at 3> found.');
$converter = new CssSelectorConverter();
$converter->toXPath('h1:');
}
/** @var FunctionNode $function */
$function = $selectors[0]->getTree();
- $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
+ $this->expectException('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
Parser::parseSeries($function->getArguments());
}
public function testFailToGetNextIdentifier()
{
- $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
+ $this->expectException('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
$stream = new TokenStream();
$stream->push(new Token(Token::TYPE_DELIMITER, '.', 2));
public function testFailToGetNextIdentifierOrStar()
{
- $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
+ $this->expectException('Symfony\Component\CssSelector\Exception\SyntaxErrorException');
$stream = new TokenStream();
$stream->push(new Token(Token::TYPE_DELIMITER, '.', 2));
$this->assertEquals($xpath, $translator->cssToXPath($css, ''));
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testCssToXPathPseudoElement()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$translator->cssToXPath('e::first-line');
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testGetExtensionNotExistsExtension()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$translator->getExtension('fake');
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testAddCombinationNotExistsExtension()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$parser = new Parser();
$translator->addCombination('fake', $xpath, $combinedXpath);
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testAddFunctionNotExistsFunction()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$xpath = new XPathExpr();
$translator->addFunction($xpath, $function);
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testAddPseudoClassNotExistsClass()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$xpath = new XPathExpr();
$translator->addPseudoClass($xpath, 'fake');
}
- /**
- * @expectedException \Symfony\Component\CssSelector\Exception\ExpressionErrorException
- */
public function testAddAttributeMatchingClassNotExistsClass()
{
+ $this->expectException('Symfony\Component\CssSelector\Exception\ExpressionErrorException');
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$xpath = new XPathExpr();
$elements = $document->xpath($translator->cssToXPath($css));
$this->assertCount(\count($elementsId), $elements);
foreach ($elements as $element) {
- $this->assertTrue(\in_array($element->attributes()->id, $elementsId));
+ $this->assertContains((string) $element->attributes()->id, $elementsId);
}
}
$this->assertCount(\count($elementsId), $elementsId);
foreach ($elements as $element) {
if (null !== $element->attributes()->id) {
- $this->assertTrue(\in_array($element->attributes()->id, $elementsId));
+ $this->assertContains((string) $element->attributes()->id, $elementsId);
}
}
libxml_clear_errors();
$this->assertCount($count, $elements);
}
+ public function testOnlyOfTypeFindsSingleChildrenOfGivenType()
+ {
+ $translator = new Translator();
+ $translator->registerExtension(new HtmlExtension($translator));
+ $document = new \DOMDocument();
+ $document->loadHTML(<<<'HTML'
+<html>
+ <body>
+ <p>
+ <span>A</span>
+ </p>
+ <p>
+ <span>B</span>
+ <span>C</span>
+ </p>
+ </body>
+</html>
+HTML
+);
+
+ $xpath = new \DOMXPath($document);
+ $nodeList = $xpath->query($translator->cssToXPath('span:only-of-type'));
+
+ $this->assertSame(1, $nodeList->length);
+ $this->assertSame('A', $nodeList->item(0)->textContent);
+ }
+
public function getXpathLiteralTestData()
{
return [
['e:first-of-type', '*/e[position() = 1]'],
['e:last-of-type', '*/e[position() = last()]'],
['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"],
- ['e:only-of-type', 'e[last() = 1]'],
+ ['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],
['e:empty', 'e[not(*) and not(string-length())]'],
['e:EmPTY', 'e[not(*) and not(string-length())]'],
['e:root', 'e[not(parent::*)]'],
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param string $attribute
- * @param string $value
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*/
}
/**
- * @param XPathExpr $xpath
- * @param FunctionNode $function
- * @param bool $last
- * @param bool $addNameTest
+ * @param bool $last
+ * @param bool $addNameTest
*
* @return XPathExpr
*
*/
public function translateOnlyOfType(XPathExpr $xpath)
{
- if ('*' === $xpath->getElement()) {
+ $element = $xpath->getElement();
+
+ if ('*' === $element) {
throw new ExpressionErrorException('"*:only-of-type" is not implemented.');
}
- return $xpath->addCondition('last() = 1');
+ return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
}
/**
}
/**
- * @param string $combiner
- * @param NodeInterface $xpath
- * @param NodeInterface $combinedXpath
+ * @param string $combiner
*
* @return XPathExpr
*
}
/**
- * @param XPathExpr $xpath
- * @param string $pseudoClass
+ * @param string $pseudoClass
*
* @return XPathExpr
*
}
/**
- * @param XPathExpr $xpath
- * @param string $operator
- * @param string $attribute
- * @param string $value
+ * @param string $operator
+ * @param string $attribute
+ * @param string $value
*
* @return XPathExpr
*
/**
* Translates a parsed selector node to an XPath expression.
*
- * @param SelectorNode $selector
- * @param string $prefix
+ * @param string $prefix
*
* @return string
*/