/** * Parses the namespace rule. * * @param string $ruleString */ protected function parseRuleString($ruleString) { if (is_string($ruleString)) { $charset = $this->getCharset(); // Remove at-rule and unnecessary white-spaces $ruleString = preg_replace('/^[ \\r\\n\\t\\f]*@namespace[ \\r\\n\\t\\f]+/i', '', $ruleString); $ruleString = trim($ruleString, " \r\n\t\f"); // Remove trailing semicolon $ruleString = rtrim($ruleString, ";"); $isEscaped = false; $inFunction = false; $parts = []; $currentPart = ""; for ($i = 0, $j = mb_strlen($ruleString, $charset); $i < $j; $i++) { $char = mb_substr($ruleString, $i, 1, $charset); if ($char === "\\") { if ($isEscaped === false) { $isEscaped = true; } else { $isEscaped = false; } } else { if ($char === " ") { if ($isEscaped === false) { if ($inFunction == false) { $currentPart = trim($currentPart, " \r\n\t\f"); if ($currentPart !== "") { $parts[] = trim($currentPart, " \r\n\t\f"); $currentPart = ""; } } } else { $currentPart .= $char; } } elseif ($isEscaped === false && $char === "(") { $inFunction = true; $currentPart .= $char; } elseif ($isEscaped === false && $char === ")") { $inFunction = false; $currentPart .= $char; } else { $currentPart .= $char; } } // Reset escaped flag if ($isEscaped === true && $char !== "\\") { $isEscaped = false; } } if ($currentPart !== "") { $currentPart = trim($currentPart, " \r\n\t\f"); if ($currentPart !== "") { $parts[] = trim($currentPart, " \r\n\t\f"); } } foreach ($parts as $key => $value) { $parts[$key] = Placeholder::replaceStringPlaceholders($value); } $countParts = count($parts); if ($countParts === 2) { $this->setPrefix($parts[0]); // Get URL value $name = Url::extractUrl($parts[1]); $this->setName($name); } elseif ($countParts === 1) { // Get URL value $name = Url::extractUrl($parts[0]); $this->setName($name); } else { // ERROR } } else { throw new \InvalidArgumentException("Invalid type '" . gettype($ruleString) . "' for argument 'ruleString' given."); } }
/** * Parses the import rule. * * @param string $ruleString */ protected function parseRuleString($ruleString) { $charset = $this->getCharset(); // Remove at-rule and unnecessary white-spaces $ruleString = preg_replace('/^[ \\r\\n\\t\\f]*@import[ \\r\\n\\t\\f]+/i', '', $ruleString); $ruleString = trim($ruleString, " \r\n\t\f"); // Remove trailing semicolon $ruleString = rtrim($ruleString, ";"); $isEscaped = false; $inFunction = false; $url = ""; $mediaQuery = ""; $currentPart = ""; for ($i = 0, $j = mb_strlen($ruleString, $charset); $i < $j; $i++) { $char = mb_substr($ruleString, $i, 1, $charset); if ($char === "\\") { if ($isEscaped === false) { $isEscaped = true; } else { $isEscaped = false; } } else { if ($char === " ") { if ($isEscaped === false) { if ($inFunction == false) { $currentPart = trim($currentPart, " \r\n\t\f"); if ($currentPart !== "") { if ($url === "") { $url = trim($currentPart, " \r\n\t\f"); } else { $mediaQuery .= trim($currentPart, " \r\n\t\f"); $mediaQuery .= $char; } $currentPart = ""; } } } else { $currentPart .= $char; } } elseif ($isEscaped === false && $char === "(") { $inFunction = true; $currentPart .= $char; } elseif ($isEscaped === false && $char === ")") { $inFunction = false; $currentPart .= $char; } else { $currentPart .= $char; } } // Reset escaped flag if ($isEscaped === true && $char !== "\\") { $isEscaped = false; } } if ($currentPart !== "") { $currentPart = trim($currentPart, " \r\n\t\f"); if ($currentPart !== "") { if ($url === "") { $url = trim($currentPart, " \r\n\t\f"); } else { $mediaQuery .= trim($currentPart, " \r\n\t\f"); } } } // Get URL value $url = Url::extractUrl($url); $this->setUrl($url); // Process media query $mediaRule = new MediaRule($mediaQuery); $this->setQueries($mediaRule->getQueries()); }
/** * Generates the content for the given rules, depending on the set format options. * * @param RuleAbstract[] $rules * @param int $level * @return string */ protected function getRulesContent(array $rules = null, $level = 0) { if ($level === 0) { $rules = $this->getStyleSheet()->getRules(); $this->errorMessages = []; } // Get format options $options = $this->getOptions($level); $content = ""; foreach ($rules as $rule) { if ($options["BaseAddComments"] === true) { if (!$rule instanceof CharsetRule) { $comments = $rule->getComments(); foreach ($comments as $comment) { $content .= $options["CommentIntend"] . $comment . $options["CommentLineBreak"]; } } } // Skip invalid rules if ($rule->getIsValid() === false) { // Copy error to the writer foreach ($rule->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } continue; } if ($rule instanceof CharsetRule) { // @charset must be at the beginning of the content if ($content === "") { $content .= "@" . (string) $rule->getVendorPrefix() . "charset \"" . $rule->getValue() . "\";" . $options["CharsetLineBreak"]; } } elseif ($rule instanceof ImportRule) { $content .= $options["BaseIntend"]; $content .= "@" . (string) $rule->getVendorPrefix() . "import "; $content .= "url(\"" . Url::escapeUrl($rule->getUrl()) . "\")"; $mediaQueryConcat = ""; foreach ($rule->getQueries() as $mediaQuery) { $content .= $mediaQueryConcat; if ($mediaQuery->getIsOnly() === true) { $content .= " only"; } else { if ($mediaQuery->getIsNot() === true) { $content .= " not"; } } // Filter default type "all" if ($mediaQuery->getType() !== MediaQuery::TYPE_ALL) { $content .= " " . $mediaQuery->getType(); } $conditions = $mediaQuery->getConditions(); if (count($conditions) > 0) { if ($mediaQuery->getType() !== MediaQuery::TYPE_ALL) { $content .= " and"; } $content .= " ("; $mediaConditionConcat = ""; foreach ($conditions as $condition) { if ($condition->getIsValid() === true) { $content .= $mediaConditionConcat . $condition->getValue(); $mediaConditionConcat = ") and ("; } else { // Copy error to the writer foreach ($condition->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } $content .= ")"; } $mediaQueryConcat = ","; } $content .= ";" . $options["ImportLineBreak"]; } elseif ($rule instanceof NamespaceRule) { $content .= $options["BaseIntend"]; $content .= "@" . (string) $rule->getVendorPrefix() . "namespace"; $prefix = $rule->getPrefix(); if (!empty($prefix)) { $content .= " " . $prefix; } $content .= " url(\"" . Url::escapeUrl($rule->getName()) . "\")"; $content .= ";" . $options["NamespaceLineBreak"]; } elseif ($rule instanceof DocumentRule) { // Prepare rule start content $ruleStartContent = $options["BaseIntend"]; $ruleStartContent .= "@" . (string) $rule->getVendorPrefix() . "document"; // Prepare rule filter content $concat = " "; $url = $rule->getUrl(); if (!empty($url)) { $ruleStartContent .= $concat . "url({$url})"; $concat = $options["DocumentFilterSeparator"]; } $urlPrefix = $rule->getUrlPrefix(); if (!empty($urlPrefix)) { $ruleStartContent .= $concat . "url-prefix({$urlPrefix})"; $concat = $options["DocumentFilterSeparator"]; } $domain = $rule->getDomain(); if (!empty($domain)) { $ruleStartContent .= $concat . "domain({$domain})"; $concat = $options["DocumentFilterSeparator"]; } $regexp = $rule->getRegexp(); if (!empty($regexp)) { $ruleStartContent .= $concat . "regexp({$regexp})"; } // Prepare rule content $ruleRuleContent = $this->getRulesContent($rule->getRules(), $level + 1); // Only add the content if valid rules set if ($ruleRuleContent !== "") { $content .= $ruleStartContent; $content .= $options["DocumentRuleSetOpen"]; $content .= $ruleRuleContent; $content .= $options["DocumentRuleSetClose"]; } else { $this->errorMessages[] = "Empty document at-rule '{$ruleStartContent}' ignored."; } } elseif ($rule instanceof FontFaceRule) { // Prepare rule start content $ruleStartContent = $options["BaseIntend"]; $ruleStartContent .= "@" . (string) $rule->getVendorPrefix() . "font-face"; $ruleStartContent .= $options["FontFaceRuleSetOpen"]; // Prepare rule declarations content /** @var FontFaceDeclaration[] $declarations */ $declarations = $rule->getDeclarations(); for ($i = 0, $j = count($declarations); $i < $j; $i++) { if ($declarations[$i]->getIsValid() === true) { if ($ruleStartContent !== "") { $content .= $ruleStartContent; $ruleStartContent = ""; } if ($options["BaseAddComments"] === true) { $comments = $declarations[$i]->getComments(); foreach ($comments as $comment) { $content .= $options["FontFaceRuleSetIntend"] . $comment . $options["FontFaceCommentLineBreak"]; } } $content .= $options["FontFaceDeclarationIntend"] . $declarations[$i]->getProperty(); $content .= $options["FontFaceDeclarationSeparator"] . $declarations[$i]->getValue(); if ($options["BaseLastDeclarationSemicolon"] === true || $i < $j - 1) { $content .= ";"; } $content .= $options["FontFaceDeclarationLineBreak"]; } else { // Copy error to the writer foreach ($declarations[$i]->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } // Only add the content if valid declarations set if ($ruleStartContent === "") { $content .= $options["FontFaceRuleSetClose"]; } } elseif ($rule instanceof KeyframesRule) { // Prepare rule content $ruleStartContent = $options["BaseIntend"]; $ruleStartContent .= "@" . (string) $rule->getVendorPrefix() . "keyframes "; $ruleStartContent .= $rule->getIdentifier(); $ruleRulesContent = $this->getRulesContent($rule->getRules(), $level + 1); // Only add the content if valid rules set if ($ruleRulesContent !== "") { $content .= $ruleStartContent; $content .= $options["KeyframesRuleSetOpen"]; $content .= $ruleRulesContent; $content .= $options["KeyframesRuleSetClose"]; } else { $this->errorMessages[] = "Empty keyframes at-rule '{$ruleStartContent}' ignored."; } } elseif ($rule instanceof MediaRule) { // Prepare rule content $ruleStartContent = $options["BaseIntend"]; $ruleStartContent .= "@" . (string) $rule->getVendorPrefix() . "media"; $mediaQueryConcat = " "; $ignoreAllType = true; foreach ($rule->getQueries() as $mediaQuery) { $ruleStartContent .= $mediaQueryConcat; if ($mediaQuery->getIsOnly() === true) { $ruleStartContent .= "only "; $ignoreAllType = false; } elseif ($mediaQuery->getIsNot() === true) { $ruleStartContent .= "not "; $ignoreAllType = false; } if ($ignoreAllType === false || $mediaQuery->getType() !== MediaQuery::TYPE_ALL) { $ruleStartContent .= $mediaQuery->getType(); } $conditions = $mediaQuery->getConditions(); if (count($conditions) > 0) { if ($ignoreAllType === false || $mediaQuery->getType() !== MediaQuery::TYPE_ALL) { $ruleStartContent .= " and "; } $ruleStartContent .= "("; $mediaConditionConcat = ""; foreach ($conditions as $condition) { if ($condition->getIsValid() === true) { $ruleStartContent .= $mediaConditionConcat . $condition->getValue(); $mediaConditionConcat = ") and ("; } else { // Copy error to the writer foreach ($condition->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } $ruleStartContent .= ")"; } $mediaQueryConcat = $options["MediaQuerySeparator"]; $ignoreAllType = false; } $ruleRulesContent = $this->getRulesContent($rule->getRules(), $level + 1); // Only add the content if valid rules set if ($ruleRulesContent !== "") { $content .= $ruleStartContent; $content .= $options["MediaRuleSetOpen"]; $content .= $ruleRulesContent; $content .= $options["MediaRuleSetClose"]; } else { $this->errorMessages[] = "Empty media at-rule '{$ruleStartContent}' ignored."; } } elseif ($rule instanceof PageRule) { $content .= $options["BaseIntend"]; $content .= "@" . (string) $rule->getVendorPrefix() . "page"; $selector = $rule->getSelector()->getValue(); if ($selector !== PageSelector::SELECTOR_ALL) { $content .= " " . $selector; } $content .= $options["PageRuleSetOpen"]; /** @var PageDeclaration[] $declarations */ $declarations = $rule->getDeclarations(); for ($i = 0, $j = count($declarations); $i < $j; $i++) { if ($declarations[$i]->getIsValid() === true) { if ($options["BaseAddComments"] === true) { $comments = $declarations[$i]->getComments(); foreach ($comments as $comment) { $content .= $options["PageDeclarationIntend"] . $comment . $options["PageCommentLineBreak"]; } } $content .= $options["PageDeclarationIntend"] . $declarations[$i]->getProperty(); $content .= $options["PageDeclarationSeparator"] . $declarations[$i]->getValue(); if ($options["BaseLastDeclarationSemicolon"] === true || $i < $j - 1) { $content .= ";"; } $content .= $options["PageDeclarationLineBreak"]; } else { // Copy error to the writer foreach ($declarations[$i]->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } $content .= $options["PageRuleSetClose"]; } elseif ($rule instanceof SupportsRule) { // Prepare rule content $ruleStartContent = $options["BaseIntend"]; $ruleStartContent .= "@" . (string) $rule->getVendorPrefix() . "supports "; $conditions = $rule->getConditions(); if (count($conditions) > 0) { $ruleStartContent .= "("; $supportConditionConcat = ""; foreach ($conditions as $condition) { if ($condition->getIsValid() === true) { $ruleStartContent .= $supportConditionConcat . $condition->getValue(); $supportConditionConcat = ") and ("; } else { // Copy error to the writer foreach ($condition->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } $ruleStartContent .= ")"; } $ruleRulesContent = $this->getRulesContent($rule->getRules(), $level + 1); // Only add the content if valid rules set if ($ruleRulesContent !== "") { $content .= $ruleStartContent; $content .= $options["SupportsRuleSetOpen"]; $content .= $ruleRulesContent; $content .= $options["SupportsRuleSetClose"]; } else { $this->errorMessages[] = "Empty supports at-rule '{$ruleStartContent}' ignored."; } } elseif ($rule instanceof StyleRuleSet) { // Prepare rule content $ruleStartContent = $options["BaseIntend"]; $ruleSelectorContent = ""; $concat = ""; foreach ($rule->getSelectors() as $selector) { if ($selector->getIsValid() === true) { $ruleSelectorContent .= $concat . $selector->getValue(); $concat = $options["StyleSelectorSeparator"]; } else { // Copy error to the writer foreach ($selector->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } // Only add the content if valid selectors set if ($ruleSelectorContent !== "") { $ruleStartContent .= $ruleSelectorContent; $ruleStartContent .= $options["StyleDeclarationsOpen"]; // Prepare rule declaration content /** @var StyleDeclaration[] $declarations */ $ruleDeclarationContent = ""; $declarations = $rule->getDeclarations(); for ($i = 0, $j = count($declarations); $i < $j; $i++) { if ($declarations[$i]->getIsValid() === true) { if ($options["BaseAddComments"] === true) { $comments = $declarations[$i]->getComments(); foreach ($comments as $comment) { $ruleDeclarationContent .= $options["StyleDeclarationIntend"] . $comment . $options["StyleCommentLineBreak"]; } } $important = $declarations[$i]->getIsImportant() ? " !important" : ""; $ruleDeclarationContent .= $options["StyleDeclarationIntend"] . $declarations[$i]->getProperty(); $ruleDeclarationContent .= $options["StyleDeclarationSeparator"] . $declarations[$i]->getValue() . $important; if ($options["BaseLastDeclarationSemicolon"] === true || $i < $j - 1) { $ruleDeclarationContent .= ";"; } $ruleDeclarationContent .= $options["StyleDeclarationLineBreak"]; } else { // Copy error to the writer foreach ($declarations[$i]->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } // Only add the content if valid declarations set if ($ruleDeclarationContent !== "") { $content .= $ruleStartContent; $content .= $ruleDeclarationContent; $content .= $options["StyleDeclarationsClose"]; } else { $this->errorMessages[] = "Empty style rule set '{$ruleSelectorContent}' ignored."; } } } elseif ($rule instanceof KeyframesRuleSet) { // Prepare rule content $ruleStartContent = $options["BaseIntend"]; $ruleSelectorContent = ""; $concat = ""; foreach ($rule->getKeyframes() as $keyframe) { if ($keyframe->getIsValid() === true) { $ruleSelectorContent .= $concat . $keyframe->getValue(); $concat = $options["KeyframesSelectorSeparator"]; } else { // Copy error to the writer foreach ($keyframe->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } // Only add the content if valid selectors set if ($ruleSelectorContent !== "") { $ruleStartContent .= $ruleSelectorContent; $ruleStartContent .= $options["KeyframesDeclarationsOpen"]; /** @var KeyframesDeclaration[] $declarations */ $ruleDeclarationContent = ""; $declarations = $rule->getDeclarations(); for ($i = 0, $j = count($declarations); $i < $j; $i++) { if ($declarations[$i]->getIsValid() === true) { $ruleDeclarationContent .= $options["KeyframesDeclarationIntend"] . $declarations[$i]->getProperty(); $ruleDeclarationContent .= $options["KeyframesDeclarationSeparator"] . $declarations[$i]->getValue(); if ($options["BaseLastDeclarationSemicolon"] === true || $i < $j - 1) { $ruleDeclarationContent .= ";"; } $ruleDeclarationContent .= $options["KeyframesDeclarationLineBreak"]; } else { // Copy error to the writer foreach ($declarations[$i]->getValidationErrors() as $validationError) { $this->errorMessages[] = $validationError; } } } // Only add the content if valid declarations set if ($ruleDeclarationContent !== "") { $content .= $ruleStartContent; $content .= $ruleDeclarationContent; $content .= $options["KeyframesDeclarationsClose"]; } else { $this->errorMessages[] = "Empty keyframes rule set '{$ruleSelectorContent}' ignored."; } } } } // Save complete style sheet content in property if ($level === 0) { $this->content = $content; } return $content; }