GithubHelp home page GithubHelp logo

tbela99 / css Goto Github PK

View Code? Open in Web Editor NEW
14.0 14.0 1.0 18.89 MB

A CSS parser and minifier and sourcemap generator written in PHP

License: Other

PHP 99.99% Batchfile 0.01%
ast beautifier compressor css css-nesting css-parser minifier parser php php-css-beautify php-css-minifier php-css-parser sourcemap

css's People

Contributors

imgbotapp avatar tbela99 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

shish

css's Issues

Add a method to traverse a node

it should be possible to do this instead of initializing a Traverser instance

$element->traverse(function ($node) {

// do something with the node
}, 'enter');

fix documentation

  • missing documentation for Parser#options capture_errors
  • incorrect Parser#options documentation

Performance improvements

parsing optimizations

  • eliminate recursive calls
  • remove redundant code
  • multiprocessing

renderer optimization

  • eliminate recursive calls, if possible
  • remove redundant code
  • multprocessing

windows compatibility

  • cli interface for windows
  • ๐Ÿšฉ windows compatibility test: two failing test cases (Sourcemap and MultiProcessing) and need to address LF to CRLF conversion by git

Shortening 0(unit) to 0 breaks declarations in some cases

Input CSS:

textarea {
  border: 0px;
  padding: 10px 0px 12px 35px;
  transition: all 0s ease-in-out;
}

Code:

$input = file_get_contents('test.css');
$parser = new Parser();
$parser->setContent($input);
$stylesheet = $parser->parse();
echo $stylesheet;

Output CSS:

textarea {
 border: 0;
 padding: 10px 0 12px 35px;
 transition: all 0 ease-in-out
}

border and padding are valid, while transition is not. It doesn't work in browsers and doesn't pass W3 Validator.
From what I can see transition properties like transition-duration and transition-delay always require unit to be present, even if the value is 0. Not sure if there are other properties like this.

Quotes and extra character

@tbela99, Thanks for the previous fixes! And sorry, but I am writing again with found problems.

2 problems.

Code:

$test = '#test .test2{}#test3 .test4{color:scroll;}';

Result:

#test .test2 {

}
}#test3 .test4 {
 color: scroll
}

An extra character "}" appears after an empty style (no space).

Quotes again (any param in tag):

$test = 'div[data-elem-id="1587819236980"]{background:red;}';

Result:

div[data-elem-id=1587819236980] {
 background: red
}

Correct:

div[data-elem-id="1587819236980"]{
background:red;
}

HTML for test:

<div data-elem-id="1587819236980">
	test
</div>

1
2

Margin: 0 auto

Thanks for fixing the previous issue!

New one:

.t228__maincontainer.t228__c12collumns{max-width:1200px;margin:0 auto}`

Result:

.t228__maincontainer.t228__c12collumns { max-width: 1200px; margin-top: 0 auto; margin-right: 0 auto; margin-bottom: 0 auto; margin-left: 0 auto }

Is wrong:

margin-top: 0 auto;
margin-right: 0 auto;
margin-bottom: 0 auto;
margin-left: 0 auto;

Correct:

margin-top: 0;
margin-right: auto;
margin-bottom: 0;
margin-left: auto;

Option:

'allow_duplicate_declarations' => false

Fix.

File: Renderer.php

After:

if ($value == 'none' && in_array($name, ['border', 'border-top', 'border-right', 'border-left', 'border-bottom', 'outline'])) {

Something like:

        if ($value == '0 auto' && in_array($name, ['margin-top', 'margin-right', 'margin-left', 'margin-bottom']) ) {
            if($name == 'margin-top' || $name == 'margin-bottom'){
                $value = 0;
            }else{
                $value = 'auto';
            }
        }
        if ($value == 'auto 0' && in_array($name, ['margin-top', 'margin-right', 'margin-left', 'margin-bottom']) ) {
            if($name == 'margin-left' || $name == 'margin-right'){
                $value = 'auto';
            }else{
                $value = 0;
            }
        }

Calc

@tbela99, Thanks for the previous edits! ...Here We Go Again...

Now "calc":

$test = 'div[data-elem-id="1587819338886"] { color: #000000; z-index: 5; top: calc(50vh - 375px + 325px); left: calc(50% - 600px + 26px); width: 610px; background: red;}';

Result:

div[data-elem-id="1587819338886"] {
 color: #000;
 z-index: 5;
 top: calc(50vh- 375px+325px);
 left: calc(50%- 600px+26px);
 width: 610px;
 background: red
}

Correct:

div[data-elem-id="1587819338886"] {
	color: #000000;
	z-index: 5;
	top: calc(50vh - 375px + 325px);
	left: calc(50% - 600px + 26px);
	width: 610px;
	background: red;
}

compute background shorthand

  • background-attachment
  • background-clip
  • background-color
  • background-image
  • background-origin
  • background-position
  • background-repeat
  • background-size
  • background
  • multiple backgrounds

incorrectly rendering computed rules when they use !important

these rules are incorrectly rendered

echo new Parser('
  .btnflexanimate:hover{
      background:transparent !important;
      background-color:transparent !important;
  }');
echo new Parser('
 
  .btnflexanimate:hover{
      margin: 2px !important;
      margin-left: 3px !important;
  }');

Removing last selector causes exception

I have the following CSS:

.widget-rss.red .title,
.widget-recent .title {
  color: red;
}
aside .widget-rss:hover {
  background: #fff;
}

I'm trying to remove .widget-rss from selectors to get this:

.widget-recent .title {
  color: red;
}

I'm using the following code:

foreach ($stylesheet->query("[value*='.widget-rss']") as $p) {
  foreach($p->getSelector() as $selector) {
    if(strpos($selector, '.widget-rss') !== false) {
      $p->removeSelector($selector);
    }
  }
}
$output = $renderer->render($stylesheet);

And I'm getting exception:

Fatal error: Uncaught Exception: invalid token '.widget-recent .title' in C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Value.php:79
Stack trace:
#0 C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Renderer.php(631): TBela\CSS\Value::renderTokens()
#1 C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Renderer.php(329): TBela\CSS\Renderer->renderSelector()
#2 C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Renderer.php(481): TBela\CSS\Renderer->renderRule()
#3 C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Renderer.php(105): TBela\CSS\Renderer->renderCollection()
#4 C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Renderer.php(77): TBela\CSS\Renderer->renderAst()
#5 C:\Projects\TestProject\test.php(58): TBela\CSS\Renderer->render()
#6 {main}
  thrown in C:\Projects\TestProject\vendor\tbela99\css\src\TBela\CSS\Value.php on line 79

I think I know why. When I'm calling removeSelector

$this->ast->selector = array_diff($this->ast->selector, $selector);

it uses array_diff. This function preserves array keys. As a result, since .widget-rss.red .title gets removed, there is a gap in selector array at index 0.
This causes array to not pass the following check in Renderer cause it specifically checks for index 0
if (is_array($selector) && is_string($selector[0])) {

My suggestion is to alter removeSelector function from this:

$this->ast->selector = array_diff($this->ast->selector, $selector);

to this

$this->ast->selector = array_values(array_diff($this->ast->selector, $selector));

vendor prefix declarations not rendered properly

.site-grid>[class*=" container-"], .site-grid>[class^=container-] {
    -webkit-column-gap: 1em;
    -moz-column-gap: 1em;
    column-gap: 1em;
    max-width: none;
    width: 100%;
}

is incorrectly rendered as

site-grid>[class*=" container-"],
.site-grid>[class^=container-] {
    -moz-column-gap: 1em;
    max-width: none;
    width: 100%;
}

instead of

.site-grid>[class*=" container-"],
.site-grid>[class^=container-] {
 -webkit-column-gap: 1em;
 -moz-column-gap: 1em;
 column-gap: 1em;
 max-width: none;
 width: 100%
}

SVG problem ";"

Code:

use \TBela\CSS\Parser; use \TBela\CSS\Renderer; $test = ' .test{background-color:red;}.t228__white-black .ya-share2__container_size_m .ya-share2__item_service_facebook .ya-share2__icon {background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMjIyIiB2aWV3Qm94PSIwIDAgMjggMjgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE1LjEgMjN2LTguMjFoMi43NzNsLjQxNS0zLjJIMTUuMVY5LjU0N2MwLS45MjcuMjYtMS41NTggMS41OTYtMS41NThsMS43MDQtLjAwMlY1LjEyNkEyMi43ODcgMjIuNzg3IDAgMCAwIDE1LjkxNyA1QzEzLjQ2IDUgMTEuNzggNi40OTIgMTEuNzggOS4yM3YyLjM2SDl2My4yaDIuNzhWMjNoMy4zMnoiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==) }.test{color:red;}';$parser = new Parser($test);$element = $parser->parse();echo $element;

Result:

.test { background-color: red } .t228__white-black .ya-share2__container_size_m .ya-share2__item_service_facebook .ya-share2__icon { background-image: url(data:image/svg+xmlurl } .test { color: red }

Not fully correct fix:

Parser.php

$statement = static::substr($block, $i, $j, [';', '}']);

After add:

if (strpos($statement,'svg+xml') !== false) { $statement = static::substr($block, $i, $j, ['}']); }

Problem:

.t228__white-black .ya-share2__container_size_m .ya-share2__item_service_facebook .ya-share2__icon { font-size: 14px; background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMjIyIiB2aWV3Qm94PSIwIDAgMjggMjgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE1LjEgMjN2LTguMjFoMi43NzNsLjQxNS0zLjJIMTUuMVY5LjU0N2MwLS45MjcuMjYtMS41NTggMS41OTYtMS41NThsMS43MDQtLjAwMlY1LjEyNkEyMi43ODcgMjIuNzg3IDAgMCAwIDE1LjkxNyA1QzEzLjQ2IDUgMTEuNzggNi40OTIgMTEuNzggOS4yM3YyLjM2SDl2My4yaDIuNzhWMjNoMy4zMnoiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==); color:red }

No indentation is added after the attribute.

incomplete tokens handling

according to https://www.w3.org/TR/css-syntax-3/, if the stylesheet ends while any rule, declaration, function, string, etc. are still open, everything is automatically closed.

  • invalid at-rule
  • invalid rule
  • invalid declaration
  • invalid declaration with invalid function
  • invalid declaration with invalid string
  • invalid declaration with invalid function and comment
  • invalid declaration with invalid string and comment
  • invalid comment

@font-face "src" - missed '

$test = "@font-face{font-family:'CenturyGothic';src:url('/CenturyGothic.woff') format('woff');font-weight:400;}";

Result:

@font-face { font-family: CenturyGothic; font-weight: 400; src: url(/CenturyGothic.woff) format(woff) }

Correctly:

@font-face { font-family: CenturyGothic; font-weight: 400; src: url(/CenturyGothic.woff) format('woff') }

RC1

fix infinite loop with bad css syntax

Subsequent-sibling Combinator 'swallows' characters that follow it

Input CSS:

.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),	
.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~figure.wp-block-image:not(#individual-image) {	
  width: calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*0.5)	
}

Code:

$input = file_get_contents('test.css');
$parser = new Parser();
$parser->setContent($input);
$stylesheet = $parser->parse();
echo $stylesheet;

Output:

.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2),
.wp-block-gallery.has-nested-images.columns-default figure.wp-block-image:not(#individual-image):first-child:nth-last-child(2)~gure.wp-block-image:not(#individual-image) {
 width: calc(50% - var(--wp--style--unstable-gallery-gap, 16px)*0.5)
}

As you can see, during the parsing the "fi" in "figure" after tilde ~ disappeared.

I tracked it to an increment in the following line. Removing increment solves the issue.

$tokens[] = (object)['type' => 'css-string', 'value' => $string[$i++]];

From what I can see that part of the switch-case handles the following situations:

  • = in the attribute context. This is handled by the following line

    $tokens[] = (object)['type' => $context === 'attribute' ? 'operator' : 'css-string', 'value' => '='];

  • ~= ^= $= in the attribute context. You're using $string[$i++] to move past the = sign to not consume it again

    $tokens[] = (object)['type' => 'operator', 'value' => $string[$i++] . '='];

  • ~ in no context which becomes part of selector. From what I can see, there is no need for increment, since you're handling only one character.

    $tokens[] = (object)['type' => 'css-string', 'value' => $string[$i++]];

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.