Parse Styles

This will convert CSS text (from e.g. a <style> tag) into an
array broken down by rules and selectors.

@param string $styles Styles.
@return array Parsed styles.
765225
public static function parse_css($styles='') {
		ref\cast::string($styles, true);

		// Remove comments.
		while (false !== ($start = mb::strpos($styles, '/*'))) {
			if (false !== ($end = mb::strpos($styles, '*/'))) {
				$styles = \str_replace(
					mb::substr($styles, $start, ($end - $start + 2)),
					'',
					$styles
				);
			}
			else {
				$styles = mb::substr($styles, 0, $start);
			}
		}

		// A few more types of comment wrappers we might see.
		$styles = \str_replace(
			array('<!--', '//-->', '-->', '//<![CDATA[', '//]]>', '<![CDATA[', ']]>'),
			'',
			$styles
		);

		// Standardize quoting.
		ref\sanitize::quotes($styles);
		$styles = \str_replace("'", '"', $styles);

		// Whitespace.
		ref\sanitize::whitespace($styles, 0);

		// Early bail.
		if (! $styles) {
			return array();
		}

		// Substitute brackets for unlikely characters to make parsing easier
		// hopefully nobody's using braille in their stylesheets...
		$styles = \preg_replace('/\{(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u', '⠁', $styles);
		$styles = \preg_replace('/\}(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u', '⠈', $styles);

		// Put spaces behind and after parentheses.
		$styles = \preg_replace(
			'/\s*(\()\s*(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u',
			' (',
			$styles
		);
		$styles = \preg_replace(
			'/\s*(\))\s*(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u',
			') ',
			$styles
		);

		// Make sure {} have no whitespace on either end.
		$styles = \preg_replace('/\s*(⠁|⠈|@)\s*/u', '$1', $styles);

		// Push @ rules to their own lines.
		$styles = \str_replace('@', "\n@", $styles);
		$styles = \explode("\n", $styles);
		$styles = \array_map('trim', $styles);

		// Push all rulesets to their own lines (leaving @media-type ones on their own for now).
		foreach ($styles as $k=>$v) {
			// @rule.
			if (0 === \strpos($styles[$k], '@')) {
				// Nested, like @media.
				if (false !== \strpos($styles[$k], '⠈⠈')) {
					$styles[$k] = \preg_replace('/(⠈{2,})/u', "$1\n", $styles[$k]);
				}
				// Not nested, but has properties, like @font-face.
				elseif (false !== \strpos($styles[$k], '⠈')) {
					$styles[$k] = \str_replace('⠈', "⠈\n", $styles[$k]);
				}
				// Just a line, like @import.
				elseif (\preg_match('/;(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/', $styles[$k])) {
					$styles[$k] = \preg_replace(
						'/;(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u',
						";\n",
						$styles[$k],
						1
					);
				}

				$tmp = \explode("\n", $styles[$k]);
				$length = \count($tmp);
				for ($x = 1; $x < $length; ++$x) {
					$tmp[$x] = \str_replace('⠈', "⠈\n", $tmp[$x]);
				}
				$styles[$k] = \implode("\n", $tmp);
			}// end @.
			else {
				$styles[$k] = \str_replace('⠈', "⠈\n", $styles[$k]);
			}
		}
		$styles = \implode("\n", $styles);

		// One more quick formatting thing, we can get rid of spaces
		// between closing) and punctuation.
		$styles = \preg_replace(
			'/\)\s(,|;)(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u',
			')$1',
			$styles
		);

		// And between RGB/URL stuff.
		$styles = \preg_replace('/(url|rgba?)\s+\(/', '$1(', $styles);

		$styles = \explode("\n", $styles);
		$styles = \array_filter($styles, 'strlen');

		$out = array();

		// One more time around!
		foreach ($styles as $k=>$v) {
			$styles[$k] = \trim($styles[$k]);

			// Nested rule.
			if (
				(0 === \strpos($styles[$k], '@')) &&
				(false !== \strpos($styles[$k], '⠈⠈'))
			) {
				$tmp = constants::CSS_NESTED;

				// What kind of @ is this?
				\preg_match_all('/^@([a-z\-]+)/ui', $styles[$k], $matches);
				$tmp['@'] = mb::strtolower($matches[1][0]);

				// Find the outermost bit.
				if (false === ($start = mb::strpos($styles[$k], '⠁'))) {
					continue;
				}

				$tmp['selector'] = mb::strtolower(
					\trim(
						mb::substr($styles[$k], 0, $start)
					),
					false,
					true
				);
				$chunk = mb::substr($styles[$k], $start + 1, -1);
				$chunk = \str_replace(array('⠁', '⠈'), array('{', '}'), $chunk);
				$tmp['nest'] = static::parse_css($chunk);

				// And build the raw.
				$tmp['raw'] = $tmp['selector'] . '{';
				foreach ($tmp['nest'] as $n) {
					$tmp['raw'] .= $n['raw'];
				}
				$tmp['raw'] .= '}';
			}// end @.
			else {
				$tmp = constants::CSS_FLAT;

				if (0 === \strpos($styles[$k], '@')) {
					// What kind of @ is this?
					\preg_match_all('/^@([a-z\-]+)/ui', $styles[$k], $matches);
					$tmp['@'] = mb::strtolower($matches[1][0], false);
				}

				// A normal {k:v, k:v}.
				\preg_match_all('/^([^⠁]+)⠁([^⠈]*)⠈/u', $styles[$k], $matches);
				if (\count($matches[0])) {
					// Sorting out selectors is easy.
					$tmp['selectors'] = \explode(',', $matches[1][0]);
					$tmp['selectors'] = \array_map('trim', $tmp['selectors']);

					// Rules a little trickier.
					$rules = \explode(';', $matches[2][0]);
					$rules = \array_map('trim', $rules);
					$rules = \array_filter($rules, 'strlen');
					if (! \count($rules)) {
						continue;
					}

					foreach ($rules as $k2=>$v2) {
						$rules[$k2] = \rtrim($rules[$k2], ';') . ';';
						if (\preg_match(
							'/:(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/',
							$rules[$k2]
						)) {
							$rules[$k2] = \preg_replace(
								'/:(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/u',
								"\n",
								$rules[$k2],
								1
							);
							list($key, $value) = \explode("\n", $rules[$k2]);
							$key = mb::strtolower(\trim($key), false);
							$value = \trim($value);
							$tmp['rules'][$key] = $value;
						}
						else {
							$tmp['rules']['__NONE__'] = $value;
						}
					}

					// Build the raw.
					$tmp['raw'] = \implode(',', $tmp['selectors']) . '{';
					foreach ($tmp['rules'] as $k=>$v) {
						if ('__NONE__' === $k) {
							$tmp['raw'] .= $v;
						}
						else {
							$tmp['raw'] .= "$k:$v";
						}
					}
					$tmp['raw'] .= '}';
				}
				// Who knows?
				else {
					$styles[$k] = \str_replace(array('⠁', '⠈'), array('{', '}'), $styles[$k]);
					$styles[$k] = \trim(\rtrim(\trim($styles[$k]), ';'));
					if (\substr($styles[$k], -1) !== '}') {
						$styles[$k] .= ';';
					}
					$tmp['rules'][] = $styles[$k];
					$tmp['raw'] = $styles[$k];
				}
			}

			$out[] = $tmp;
		}

		return $out;
	}
30069
public function setStyles(array $styles)
    {
        $this->styles = [];

        foreach ($styles as $style) {
            $this->addStyle($style);
        }
    }
1138397
public function enqueueStyles($styles)
    {
        // Check styles
        if (empty($styles)) {
            return;
        }

        $defaults = [
            'src'   => false,
            'deps'  => [],
            'ver'   => false,
            'media' => 'all'
        ];

        // Iterate on all
        foreach ($styles as $handle => $opts) {
            if (!is_array($opts)) {
                continue;
            }

            // Merge options with defaults and enqueue style
            $opts = array_merge($defaults, $opts);
            wp_enqueue_style($handle, $opts['src'], $opts['deps'], $opts['ver'], $opts['media']);
        }
    }
79985
public function render(array $style)
    {
        $result = [];

        if (isset($style['feature'])) {
            $result[] = $this->renderStyle('feature', $style['feature']);
        }

        if (isset($style['element'])) {
            $result[] = $this->renderStyle('element', $style['element']);
        }

        foreach ($style['rules'] as $rule => $value) {
            $result[] = $this->renderStyle($rule, $value);
        }

        return implode('|', $result);
    }
1334333
function applyStyle($text, $styles = [])
    {
        if (!self::$supported)
            return $text;

        if (is_string($styles))
        {
            $styles = [$styles];
        }

        $output = $text;
        foreach ($styles as $style) {
            if (isset(self::$theme[$style])) {
                $output = $this->applyStyle($output, self::$theme[$style]);
            } else {
                if (isset(self::$styles[$style])) {
                    $pre  = $this->escSequence(self::$styles[$style]);
                    $post = $this->escSequence(0);
                } else {
                    $pre = $post = '';
                }

                $output = $pre . $output . $post;
            }
        }

        return $output;
    }
292723
public function parseStylesheet($stylesheet)
    {
        $parsed = array();
        $stylesheet = $this->_stripStylesheet($stylesheet);
        $stylesheet = trim(trim($stylesheet), "}");
        foreach(explode("}", $stylesheet) as $rule) {
            //Don't parse empty rules
        	if(!trim($rule))continue;
        	list($selector, $style) = explode("{", $rule, 2);
            foreach (explode(',', $selector) as $sel) {
                $parsed[] = array(trim($sel), trim(trim($style), ";"));
            }
        }

        return $parsed;
    }
237683
public function addStyles(array $styles) {
		foreach ($styles as $param => $value) {
			$this->addStyle($param, $value);
		}
		
		return $this;
	}
405630
protected function _convert( $css ) {
		$css = $this->_clean( $css );
		$css = explode( '}', $css );
		$css = array_filter( $css, 'strlen' );

		foreach ( $css as $key => $val ) {
			$css[ $key ] = explode( '{', $val );
		}

		foreach ( $css as $key => $css_block ) {
			$css[ $key ] = $this->_create_css_block( $css_block );
		}

		return $css;
	}
725687
public function addStyles(array $styles)
    {
        if (!$this->styleSet) {
            $this->styleSet = new DefaultStyleSet();
        }

        $this->styleSet->merge($styles);

        return $this;
    }
1017860
static public function formatSettings($settings)
    {
        if (isset($settings) && is_string($settings)) {
            $settings['class'] = $settings;
        }
        
        $settings['style'] = isset($settings['style']) ? $settings['style'] : '';
        if (isset($settings['width'])) {
            $settings['style'] .= ';width:' . $settings['width'] . ';max-width:' . $settings['width'] . ';';
        }
        if (isset($settings['height'])) {
            $settings['style'] .= ';height:' . $settings['height'] . ';';
        }
        $settings['style'] = trim(str_replace(';;', ';', $settings['style']), ';');
        
        return $settings;
    }
500197
protected function parseRule( $rules )
	{
		if ( is_array( $rules ) )
		{
			$rules = $this->parseArrayRule( $rules );
		}
		else
		{
			$rules = $this->parseStringRule( $rules );
		}

		$rules[0] = $this->normalizeRule( $rules[0] );

		return $rules;
	}
440979
protected function parseProperties(array $properties)
    {
        $parsed = [];

        foreach ($properties as $propertyString) {
            if (!mb_strpos($propertyString, ':')) {
                list($key, $value) = $this->parseBooleanShortcut($propertyString);
                $parsed[Type::camelCase($key)] = $value;
                continue;
            }


            list($key, $value) = explode(':', $propertyString, 2);


            if (!$this->isNestedShortCut($value)) {
                $parsed[Type::camelCase($key)] = $value;
                continue;
            }

            $type = $this->stringToType(trim($value, '[]'));

            $parsed[Type::camelCase($key)] = $type;
        }

        return $parsed;
    }
1247097
protected function parseStyleDimensions($style)
  	{
		if (is_callable($style->value)) {
			return  array(null, null, 'custom');
		}

		if (strpos($style->value, 'x') === false)
		{
			// Width given, height automagically selected to preserve aspect ratio (landscape).
			$width = $style->value;

			return  array($width, null, 'landscape');
		}

		$dimensions = explode('x', $style->value);
		$width = $dimensions[0];
		$height = $dimensions[1];

		if (empty($width))
    	{
			// Height given, width automagically selected to preserve aspect ratio (portrait).
			return  array(null, $height, 'portrait');
		}

		$resizingOption = substr($height, -1, 1);

		if ($resizingOption == '#')
		{
			// Resize, then crop.
      		$height = rtrim($height, '#');

			return  array($width, $height, 'crop');
		}

		if ($resizingOption == '!')
		{
			// Resize by exact width/height (does not preserve aspect ratio).
			$height = rtrim($height, '!');

			return  array($width, $height, 'exact');
		}

		// Let the script decide the best way to resize.
		return  array($width, $height, 'auto');
	}
1330270
protected function parseStyleDimensions($style)
    {
        if (is_callable($style) === true) {
            return [null, null, 'custom', false];
        }

        $enlarge = true;

        // Don't allow the package to enlarge an image
        if (strpos($style, '?') !== false) {
            $style = str_replace('?', '', $style);
            $enlarge = false;
        }

        if (strpos($style, 'x') === false) {
            // Width given, height automatically selected to preserve aspect ratio (landscape).
            $width = $style;

            return [$width, null, 'landscape', $enlarge];
        }

        $dimensions = explode('x', $style);
        $width = $dimensions[0];
        $height = $dimensions[1];

        if (empty($width)) {
            // Height given, width automatically selected to preserve aspect ratio (portrait).
            return [null, $height, 'portrait', $enlarge];
        }

        $resizing_option = substr($height, -1, 1);

        if ($resizing_option == '#') {
            // Resize, then crop.
            $height = rtrim($height, '#');

            return [$width, $height, 'crop', $enlarge];
        }

        if ($resizing_option == '!') {
            // Resize by exact width/height (does not preserve aspect ratio).
            $height = rtrim($height, '!');

            return [$width, $height, 'exact', $enlarge];
        }

        // Let the script decide the best way to resize.
        return [$width, $height, 'auto', $enlarge];
    }
996342
public static function cssToArray($css){
        if(!$css) {
            return array();
        }
        $css = str_replace(' /* Do not edit */', '', $css);
        $oldCssArr = explode("\n", $css);
        $newCssArr = array();
        foreach($oldCssArr as $line) {
            if(false === strpos($line, '{')) {
                continue;
            }

            preg_match('~(?P<selector>[^{]+)(\{)(?P<rules>[^}]+)\}~', $line, $matches);

            foreach($matches as $key => &$match){
                if(is_numeric($key)) {
                    continue;
                }
                $match = trim($match);
                if($key === 'rules') {
                    $ruleSet = array_filter(array_map('trim', explode(';', $match)));
                    $match = array();
                    foreach($ruleSet as $rule) {
                        $rule = array_map('trim', explode(':', $rule));
                        $match[$rule[0]] = $rule[1];
                    }
                }
            }

            $newCssArr[$matches['selector']] = $matches['rules'];
        }
        return $newCssArr;
    }
934563
private function appendStyles(array $styles)
    {
        if (empty($styles)) {
            return;
        }

        $string = '';
        $prependSemicolon = false;
        foreach ($styles as $key => $value) {
            if ($prependSemicolon) {
                $string .= '; ';
            }
            $prependSemicolon = true;
            $string .= $key.': '.$value;
        }

        $this->appendAttribute('style', $string);
    }
434901
public static function get_styled_dashes( $style, /** Currently unused. @scrutinizer ignore-unused */ Settings $settings ) {
		if ( isset( self::$styles[ $style ] ) ) {
			return new Simple_Dashes(
				self::$styles[ $style ][ self::_PARENTHETICAL ],
				self::$styles[ $style ][ self::_PARENTHETICAL_SPACE ],
				self::$styles[ $style ][ self::_INTERVAL ],
				self::$styles[ $style ][ self::_INTERVAL_SPACE ]
			);
		}

		return null;
	}