Files
dokuwiki-plugins/lib/plugins/odt/helper/cssimport.php
Trevor Batley bce7dd054a add contents
2025-10-09 15:04:29 +11:00

1499 lines
52 KiB
PHP

<?php
/**
* Helper class to read in a CSS style
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author LarsDW223
*/
// must be run within Dokuwiki
if (!defined('DOKU_INC')) die();
require_once DOKU_PLUGIN . 'odt/helper/csscolors.php';
/**
* Abstract class to define kind of enum for the CSS value types.
* Actually only used by adjustLengthValues().
*/
abstract class CSSValueType
{
const Other = 0;
const LengthValueXAxis = 1;
const LengthValueYAxis = 2;
const StrokeOrBorderWidth = 3;
// etc.
}
/**
* Class css_declaration
*
* @package CSS\css_declaration
*/
class css_declaration {
protected static $css_units = array ('em', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt',
'pc', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax');
protected $property;
protected $value;
/**
* Create a new declaration (property:value).
*
* @param string $property The property name of the declaration
* @param string $value The assigned value
*/
public function __construct($property, $value) {
$this->property = $property;
$this->value = trim($value, ';');
}
/**
* Get the property name of this declaration.
*
* @return string
*/
public function getProperty () {
return $this->property;
}
/**
* Get the value assigned to the property of this declaration.
*
* @return string
*/
public function getValue () {
return $this->value;
}
/**
* @param css_declaration[] $decls
*/
public function explode (&$decls) {
if ( empty ($this->property) ) {
return;
}
switch ($this->property) {
case 'background':
$this->explodeBackgroundShorthand ($decls);
break;
case 'font':
$this->explodeFontShorthand ($decls);
break;
case 'padding':
$this->explodePaddingShorthand ($decls);
break;
case 'margin':
$this->explodeMarginShorthand ($decls);
break;
case 'border':
$this->explodeBorderShorthand ($decls);
break;
case 'list-style':
$this->explodeListStyleShorthand ($decls);
break;
case 'flex':
$this->explodeFlexShorthand ($decls);
break;
case 'transition':
$this->explodeTransitionShorthand ($decls);
break;
case 'outline':
$this->explodeOutlineShorthand ($decls);
break;
case 'animation':
$this->explodeAnimationShorthand ($decls);
break;
case 'border-bottom':
$this->explodeBorderBottomShorthand ($decls);
break;
case 'columns':
$this->explodeColumnsShorthand ($decls);
break;
case 'column-rule':
$this->explodeColumnRuleShorthand ($decls);
break;
//FIXME: Implement all the shorthands missing
//case ...
}
}
/**
* @return bool
*/
public function isShorthand () {
switch ($this->property) {
case 'background':
case 'font':
case 'padding':
case 'margin':
case 'border':
case 'list-style':
case 'flex':
case 'transition':
case 'outline':
case 'animation':
case 'border-bottom':
case 'columns':
case 'column-rule':
return true;
break;
//FIXME: Implement all the shorthands missing
//case ...
}
return false;
}
/**
* @param css_declaration[] $decls
*/
protected function explodeBackgroundShorthand (&$decls) {
if ( $this->property == 'background' ) {
$values = preg_split ('/\s+/', $this->value);
$index = 0;
if ($index < count($values)) {
$color_done = true;
$value = $values [$index];
if ($value [0] == '#' || csscolors::isKnownColorName($value)) {
$decls [] = new css_declaration ('background-color', $value);
$index++;
} else {
switch ($value) {
case 'transparent':
case 'inherit':
case 'initial':
$decls [] = new css_declaration ('background-color', $value);
$index++;
break;
}
}
}
if ($index < count($values)) {
$decls [] = new css_declaration ('background-image', $values [$index]);
$index++;
}
if ($index < count($values)) {
$decls [] = new css_declaration ('background-repeat', $values [$index]);
$index++;
}
if ($index < count($values)) {
$decls [] = new css_declaration ('background-attachment', $values [$index]);
$index++;
}
if ($index < count($values)) {
$decls [] = new css_declaration ('background-position', $values [$index]);
$index++;
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeFontShorthand (&$decls, $setDefaults=false) {
if ( $this->property == 'font' ) {
$values = preg_split ('/\s+/', $this->value);
$font_style_set = false;
$font_variant_set = false;
$font_weight_set = false;
$font_size_set = false;
$font_family = '';
foreach ($values as $value) {
if ( $font_style_set === false ) {
$default = false;
switch ($value) {
case 'normal':
case 'italic':
case 'oblique':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('font-style', $value);
break;
default:
$default = true;
if ($setDefaults) {
$decls [] = new css_declaration ('font-style', 'normal');
}
break;
}
$font_style_set = true;
if ( $default === false ) {
continue;
}
}
if ( $font_variant_set === false ) {
$default = false;
switch ($value) {
case 'normal':
case 'small-caps':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('font-variant', $value);
break;
default:
$default = true;
if ($setDefaults) {
$decls [] = new css_declaration ('font-variant', 'normal');
}
break;
}
$font_variant_set = true;
if ( $default === false ) {
continue;
}
}
if ( $font_weight_set === false ) {
$default = false;
switch ($value) {
case 'normal':
case 'bold':
case 'bolder':
case 'lighter':
case '100':
case '200':
case '300':
case '400':
case '500':
case '600':
case '700':
case '800':
case '900':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('font-weight', $value);
break;
default:
$default = true;
if ($setDefaults) {
$decls [] = new css_declaration ('font-weight', 'normal');
}
break;
}
$font_weight_set = true;
if ( $default === false ) {
continue;
}
}
if ( $font_size_set === false ) {
$default = false;
$params = explode ('/', $value);
switch ($params [0]) {
case 'medium':
case 'xx-small':
case 'x-small':
case 'small':
case 'large':
case 'x-large':
case 'xx-large':
case 'smaller':
case 'larger':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('font-size', $params [0]);
break;
default:
$found = false;
foreach (self::$css_units as $css_unit) {
if ( strpos ($value, $css_unit) !== false ) {
$decls [] = new css_declaration ('font-size', $params [0]);
$found = true;
break;
}
}
if ( $found === false ) {
$default = true;
if ($setDefaults) {
$decls [] = new css_declaration ('font-size', 'medium');
}
}
break;
}
if ( !empty($params [1]) ) {
$decls [] = new css_declaration ('line-height', $params [1]);
} else {
if ($setDefaults) {
$decls [] = new css_declaration ('line-height', 'normal');
}
}
$font_size_set = true;
if ( $default === false ) {
continue;
}
}
// All other properties are found.
// The rest is assumed to be a font-family.
if (empty ($font_family)) {
$font_family = $value;
} else {
$font_family .= ' '.$value;
}
}
if (!empty ($font_family)) {
$decls [] = new css_declaration ('font-family', $font_family);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodePaddingShorthand (&$decls) {
if ( $this->property == 'padding' ) {
$values = preg_split ('/\s+/', $this->value);
switch (count($values)) {
case 4:
$decls [] = new css_declaration ('padding-top', $values [0]);
$decls [] = new css_declaration ('padding-right', $values [1]);
$decls [] = new css_declaration ('padding-bottom', $values [2]);
$decls [] = new css_declaration ('padding-left', $values [3]);
break;
case 3:
$decls [] = new css_declaration ('padding-top', $values [0]);
$decls [] = new css_declaration ('padding-right', $values [1]);
$decls [] = new css_declaration ('padding-left', $values [1]);
$decls [] = new css_declaration ('padding-bottom', $values [2]);
break;
case 2:
$decls [] = new css_declaration ('padding-top', $values [0]);
$decls [] = new css_declaration ('padding-bottom', $values [0]);
$decls [] = new css_declaration ('padding-right', $values [1]);
$decls [] = new css_declaration ('padding-left', $values [1]);
break;
case 1:
$decls [] = new css_declaration ('padding-top', $values [0]);
$decls [] = new css_declaration ('padding-bottom', $values [0]);
$decls [] = new css_declaration ('padding-right', $values [0]);
$decls [] = new css_declaration ('padding-left', $values [0]);
break;
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeMarginShorthand (&$decls) {
if ( $this->property == 'margin' ) {
$values = preg_split ('/\s+/', $this->value);
switch (count($values)) {
case 4:
$decls [] = new css_declaration ('margin-top', $values [0]);
$decls [] = new css_declaration ('margin-right', $values [1]);
$decls [] = new css_declaration ('margin-bottom', $values [2]);
$decls [] = new css_declaration ('margin-left', $values [3]);
break;
case 3:
$decls [] = new css_declaration ('margin-top', $values [0]);
$decls [] = new css_declaration ('margin-right', $values [1]);
$decls [] = new css_declaration ('margin-left', $values [1]);
$decls [] = new css_declaration ('margin-bottom', $values [2]);
break;
case 2:
$decls [] = new css_declaration ('margin-top', $values [0]);
$decls [] = new css_declaration ('margin-bottom', $values [0]);
$decls [] = new css_declaration ('margin-right', $values [1]);
$decls [] = new css_declaration ('margin-left', $values [1]);
break;
case 1:
$decls [] = new css_declaration ('margin-top', $values [0]);
$decls [] = new css_declaration ('margin-bottom', $values [0]);
$decls [] = new css_declaration ('margin-right', $values [0]);
$decls [] = new css_declaration ('margin-left', $values [0]);
break;
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeBorderShorthand (&$decls) {
$border_sides = array ('border-left', 'border-right', 'border-top', 'border-bottom');
if ( $this->property == 'border' ) {
$values = preg_split ('/\s+/', $this->value);
$index = 0;
$border_width_set = false;
$border_style_set = false;
$border_color_set = false;
while ( $index < 3 ) {
if ( $border_width_set === false ) {
switch ($values [$index]) {
case 'thin':
case 'medium':
case 'thick':
$decls [] = new css_declaration ('border-width', $values [$index]);
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side.'-width', $values [$index]);
}
break;
default:
if ( strpos ($values [$index], 'px') !== false ) {
$decls [] = new css_declaration ('border-width', $values [$index]);
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side.'-width', $values [$index]);
}
} else {
// There is no default value? So leave it unset.
}
break;
}
$border_width_set = true;
$index++;
continue;
}
if ( $border_style_set === false ) {
switch ($values [$index]) {
case 'none':
case 'dotted':
case 'dashed':
case 'solid':
case 'double':
case 'groove':
case 'ridge':
case 'inset':
case 'outset':
$decls [] = new css_declaration ('border-style', $values [$index]);
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side.'-style', $values [$index]);
}
break;
default:
$decls [] = new css_declaration ('border-style', 'none');
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side.'-style', 'none');
}
break;
}
$border_style_set = true;
$index++;
continue;
}
if ( $border_color_set === false ) {
$decls [] = new css_declaration ('border-color', $values [$index]);
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side.'-color', $values [$index]);
}
// This is the last value.
break;
}
}
foreach ($border_sides as $border_side) {
$decls [] = new css_declaration ($border_side, $values [0].' '.$values [1].' '.$values [2]);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeListStyleShorthand (&$decls) {
if ( $this->property == 'list-style' ) {
$values = preg_split ('/\s+/', $this->value);
$list_style_type_set = false;
$list_style_position_set = false;
$list_style_image_set = false;
foreach ($values as $value) {
if ( $list_style_type_set === false ) {
$default = false;
switch ($value) {
case 'disc':
case 'armenian':
case 'circle':
case 'cjk-ideographic':
case 'decimal':
case 'decimal-leading-zero':
case 'georgian':
case 'hebrew':
case 'hiragana':
case 'hiragana-iroha':
case 'katakana':
case 'katakana-iroha':
case 'lower-alpha':
case 'lower-greek':
case 'lower-latin':
case 'lower-roman':
case 'none':
case 'square':
case 'upper-alpha':
case 'upper-latin':
case 'upper-roman':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('list-style-type', $value);
break;
default:
$default = true;
$decls [] = new css_declaration ('list-style-type', 'disc');
break;
}
$list_style_type_set = true;
if ( $default === false ) {
continue;
}
}
if ( $list_style_position_set === false ) {
$default = false;
switch ($value) {
case 'inside':
case 'outside':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('list-style-position', $value);
break;
default:
$default = true;
$decls [] = new css_declaration ('list-style-position', 'outside');
break;
}
$list_style_position_set = true;
if ( $default === false ) {
continue;
}
}
if ( $list_style_image_set === false ) {
$decls [] = new css_declaration ('list-style-image', $value);
$list_style_image_set = true;
}
}
if ( $list_style_image_set === false ) {
$decls [] = new css_declaration ('list-style-image', 'none');
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeFlexShorthand (&$decls) {
if ( $this->property == 'flex' ) {
$values = preg_split ('/\s+/', $this->value);
if ( count($values) > 0 ) {
$decls [] = new css_declaration ('flex-grow', $values [0]);
}
if ( count($values) > 1 ) {
$decls [] = new css_declaration ('flex-shrink', $values [1]);
}
if ( count($values) > 2 ) {
$decls [] = new css_declaration ('flex-basis', $values [2]);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeTransitionShorthand (&$decls) {
if ( $this->property == 'transition' ) {
$values = preg_split ('/\s+/', $this->value);
if ( count($values) > 0 ) {
$decls [] = new css_declaration ('transition-property', $values [0]);
}
if ( count($values) > 1 ) {
$decls [] = new css_declaration ('transition-duration', $values [1]);
}
if ( count($values) > 2 ) {
$decls [] = new css_declaration ('transition-timing-function', $values [2]);
}
if ( count($values) > 3 ) {
$decls [] = new css_declaration ('transition-delay', $values [3]);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeOutlineShorthand (&$decls) {
if ( $this->property == 'outline' ) {
$values = preg_split ('/\s+/', $this->value);
$outline_color_set = false;
$outline_style_set = false;
$outline_width_set = false;
foreach ($values as $value) {
if ( $outline_color_set === false ) {
$decls [] = new css_declaration ('outline-color', $value);
$outline_color_set = true;
continue;
}
if ( $outline_style_set === false ) {
$default = false;
switch ($value) {
case 'none':
case 'hidden':
case 'dotted':
case 'dashed':
case 'solid':
case 'double':
case 'groove':
case 'ridge':
case 'inset':
case 'outset':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('outline-style', $value);
break;
default:
$default = true;
$decls [] = new css_declaration ('outline-style', 'none');
break;
}
$outline_style_set = true;
if ( $default === false ) {
continue;
}
}
if ( $outline_width_set === false ) {
$default = false;
switch ($value) {
case 'medium':
case 'thin':
case 'thick':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('outline-width', $value);
break;
default:
$found = false;
foreach (self::$css_units as $css_unit) {
if ( strpos ($value, $css_unit) !== false ) {
$decls [] = new css_declaration ('outline-width', $value);
$found = true;
break;
}
}
if ( $found === false ) {
$default = true;
$decls [] = new css_declaration ('outline-width', 'medium');
}
break;
}
$outline_width_set = true;
if ( $default === false ) {
continue;
}
}
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeAnimationShorthand (&$decls) {
if ( $this->property == 'animation' ) {
$values = preg_split ('/\s+/', $this->value);
if ( count($values) > 0 ) {
$decls [] = new css_declaration ('animation-name', $values [0]);
}
if ( count($values) > 1 ) {
$decls [] = new css_declaration ('animation-duration', $values [1]);
}
if ( count($values) > 2 ) {
$decls [] = new css_declaration ('animation-timing-function', $values [2]);
}
if ( count($values) > 3 ) {
$decls [] = new css_declaration ('animation-delay', $values [3]);
}
if ( count($values) > 4 ) {
$decls [] = new css_declaration ('animation-iteration-count', $values [4]);
}
if ( count($values) > 5 ) {
$decls [] = new css_declaration ('animation-direction', $values [5]);
}
if ( count($values) > 6 ) {
$decls [] = new css_declaration ('animation-fill-mode', $values [6]);
}
if ( count($values) > 7 ) {
$decls [] = new css_declaration ('animation-play-state', $values [7]);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeBorderBottomShorthand (&$decls) {
if ( $this->property == 'border-bottom' ) {
$values = preg_split ('/\s+/', $this->value);
$border_bottom_width_set = false;
$border_bottom_style_set = false;
$border_bottom_color_set = false;
foreach ($values as $value) {
if ( $border_bottom_width_set === false ) {
$default = false;
switch ($value) {
case 'medium':
case 'thin':
case 'thick':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('border-bottom-width', $value);
break;
default:
$found = false;
foreach (self::$css_units as $css_unit) {
if ( strpos ($value, $css_unit) !== false ) {
$decls [] = new css_declaration ('border-bottom-width', $value);
$found = true;
break;
}
}
if ( $found === false ) {
$default = true;
$decls [] = new css_declaration ('border-bottom-width', 'medium');
}
break;
}
$border_bottom_width_set = true;
if ( $default === false ) {
continue;
}
}
if ( $border_bottom_style_set === false ) {
$default = false;
switch ($value) {
case 'none':
case 'hidden':
case 'dotted':
case 'dashed':
case 'solid':
case 'double':
case 'groove':
case 'ridge':
case 'inset':
case 'outset':
case 'initial':
case 'inherit':
$decls [] = new css_declaration ('border-bottom-style', $value);
break;
default:
$default = true;
$decls [] = new css_declaration ('border-bottom-style', 'none');
break;
}
$border_bottom_style_set = true;
if ( $default === false ) {
continue;
}
}
if ( $border_bottom_color_set === false ) {
$decls [] = new css_declaration ('border-bottom-color', $value);
$border_bottom_color_set = true;
continue;
}
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeColumnsShorthand (&$decls) {
if ( $this->property == 'columns' ) {
$values = preg_split ('/\s+/', $this->value);
if ( count($values) == 1 && $values [0] == 'auto' ) {
$decls [] = new css_declaration ('column-width', 'auto');
$decls [] = new css_declaration ('column-count', 'auto');
return;
}
if ( count($values) > 0 ) {
$decls [] = new css_declaration ('column-width', $values [0]);
}
if ( count($values) > 1 ) {
$decls [] = new css_declaration ('column-count', $values [1]);
}
}
}
/**
* @param css_declaration[] $decls
*/
protected function explodeColumnRuleShorthand (&$decls) {
if ( $this->property == 'column-rule' ) {
$values = preg_split ('/\s+/', $this->value);
if ( count($values) > 0 ) {
$decls [] = new css_declaration ('column-rule-width', $values [0]);
}
if ( count($values) > 1 ) {
$decls [] = new css_declaration ('column-rule-style', $values [1]);
}
if ( count($values) > 2 ) {
$decls [] = new css_declaration ('column-rule-color', $values [2]);
}
}
}
/**
* @param $callback
*/
public function adjustLengthValues ($callback, $rule=NULL) {
switch ($this->property) {
case 'border-width':
case 'outline-width':
case 'border-bottom-width':
case 'column-rule-width':
$this->value =
call_user_func($callback, $this->property, $this->value, CSSValueType::StrokeOrBorderWidth, $rule);
break;
case 'margin-left':
case 'margin-right':
case 'padding-left':
case 'padding-right':
case 'width':
case 'column-width':
$this->value =
call_user_func($callback, $this->property, $this->value, CSSValueType::LengthValueXAxis, $rule);
break;
case 'margin-top':
case 'margin-bottom':
case 'padding-top':
case 'padding-bottom':
case 'min-height':
case 'height':
case 'line-height':
$this->value =
call_user_func($callback, $this->property, $this->value, CSSValueType::LengthValueYAxis, $rule);
break;
case 'border':
case 'border-left':
case 'border-right':
case 'border-top':
case 'border-bottom':
$this->adjustLengthValuesBorder ($callback, $rule);
break;
// FIXME: Shorthands are currently not processed.
// Every Shorthand would need an extra function which knows if it has any length values.
// Just like the explode...Shorthand functions.
}
}
/**
* @param $callback
*/
protected function adjustLengthValuesBorder ($callback, $rule=NULL) {
switch ($this->property) {
case 'border':
case 'border-left':
case 'border-right':
case 'border-top':
case 'border-bottom':
$values = preg_split ('/\s+/', $this->value);
$width =
call_user_func($callback, $this->property, $values [0], CSSValueType::StrokeOrBorderWidth, $rule);
$this->value = $width . ' ' . $values [1] . ' ' . $values [2];
break;
}
}
/**
* @param $callback
*/
public function replaceURLPrefixes ($callback) {
if (strncmp($this->value, 'url(', 4) == 0) {
$url = substr($this->value, 4, -1);
$this->value = call_user_func($callback, $this->property, $this->value, $url);
}
}
}
/**
* Class css_rule
*
* @package CSS\css_rule
*/
class css_rule {
protected $media = NULL;
protected $selectors = array ();
/** @var css_declaration[] */
protected $declarations = array ();
/**
* @param $selector
* @param $decls
* @param null $media
*/
public function __construct($selector, $decls, $media = NULL) {
$this->media = trim ($media);
//print ("\nNew rule: ".$media."\n"); //Debuging
$this->selectors = explode (' ', $selector);
$decls = trim ($decls, '{}');
// Parse declarations
$pos = 0;
$end = strlen ($decls);
while ( $pos < $end ) {
$colon = strpos ($decls, ':', $pos);
if ( $colon === false ) {
break;
}
$semi = strpos ($decls, ';', $colon + 1);
if ( $semi === false ) {
break;
}
$property = substr ($decls, $pos, $colon - $pos);
$property = trim($property);
$value = substr ($decls, $colon + 1, $semi - ($colon + 1));
$value = trim ($value);
$values = preg_split ('/\s+/', $value);
$value = '';
foreach ($values as $part) {
if ( $part != '!important' ) {
$value .= ' '.$part;
}
}
$value = trim($value);
// Create new declaration
$declaration = new css_declaration ($property, $value);
$this->declarations [] = $declaration;
// Handle CSS shorthands, e.g. 'border'
if ( $declaration->isShorthand () === true ) {
$declaration->explode ($this->declarations);
}
$pos = $semi + 1;
}
}
/**
* @return string
*/
public function toString () {
$returnString = '';
$returnString .= "Media= \"".$this->media."\"\n";
foreach ($this->selectors as $selector) {
$returnString .= $selector.' ';
}
$returnString .= "{\n";
foreach ($this->declarations as $declaration) {
$returnString .= ' '.$declaration->getProperty ().':'.$declaration->getValue ().";\n";
}
$returnString .= "}\n";
return $returnString;
}
/**
* @param $element
* @param $classString
* @param null $media
* @return bool|int
*/
public function matches ($element, $classString, $media = NULL, $cssId=NULL) {
$media = trim ($media);
if ( !empty($this->media) && $media != $this->media ) {
// Wrong media
//print ("\nNo-Match ".$this->media."==".$media); //Debuging
return false;
}
$matches = 0;
$classes = explode (' ', $classString);
foreach ($this->selectors as $selector) {
if ( !empty($classString) ) {
foreach ($classes as $class) {
if ( $selector [0] == '.' && $selector == '.'.$class ) {
$matches++;
break;
} else if ( $selector [0] == '#' && $selector == '#'.$cssId ) {
$matches++;
break;
} else if ( $selector == $element || $selector == $element.'.'.$class ) {
$matches++;
break;
}
}
} else {
if ( $selector [0] == '#' && $selector == '#'.$cssId ) {
$matches++;
} else if ( $selector == $element ) {
$matches++;
}
}
}
// We only got a match if all selectors were matched
if ( $matches == count($this->selectors) ) {
// Return the number of matched selectors
// This enables the caller to choose the most specific rule
return $matches;
}
return false;
}
/**
* @param $name
* @return null
*/
public function getProperty ($name) {
foreach ($this->declarations as $declaration) {
if ( $name == $declaration->getProperty () ) {
return $declaration->getValue ();
}
}
return NULL;
}
/**
* @param $values
* @return null
*/
public function getProperties (&$values) {
foreach ($this->declarations as $declaration) {
$property = $declaration->getProperty ();
$value = $declaration->getValue ();
$values [$property] = $value;
}
return NULL;
}
/**
* @param $callback
*/
public function adjustLengthValues ($callback) {
foreach ($this->declarations as $declaration) {
$declaration->adjustLengthValues ($callback);
}
}
}
/**
* Class helper_plugin_odt_cssimport
*
* @package helper\cssimport
*/
class helper_plugin_odt_cssimport extends DokuWiki_Plugin {
protected $replacements = array();
protected $raw;
/** @var css_rule[] */
protected $rules = array ();
/**
* Imports CSS from a file.
* @deprecated since 3015-05-23, use importFromFile
*
* @param $filename
*/
function importFrom($filename) {
dbg_deprecated('importFromFile');
$this->importFromFile($filename);
}
/**
* @param $contents
* @return bool
*/
function importFromString($contents) {
$this->deleteComments ($contents);
return $this->importFromStringInternal ($contents);
}
/**
* Delete comments in $contents. All comments are overwritten with spaces.
* The '&' is required. DO NOT DELETE!!!
* @param $contents
*/
protected function deleteComments (&$contents) {
// Delete all comments first
$pos = 0;
$max = strlen ($contents);
$in_comment = false;
while ( $pos < $max ) {
if ( ($pos+1) < $max &&
$contents [$pos] == '/' &&
$contents [$pos+1] == '*' ) {
$in_comment = true;
$contents [$pos] = ' ';
$contents [$pos+1] = ' ';
$pos += 2;
continue;
}
if ( ($pos+1) < $max &&
$contents [$pos] == '*' &&
$contents [$pos+1] == '/' &&
$in_comment === true ) {
$in_comment = false;
$contents [$pos] = ' ';
$contents [$pos+1] = ' ';
$pos += 2;
continue;
}
if ( $in_comment === true ) {
$contents [$pos] = ' ';
}
$pos++;
}
}
/**
* @param $contents
* @param null $media
* @return bool
*/
protected function importFromStringInternal($contents, $media = NULL, &$processed = NULL) {
// Find all CSS rules
$pos = 0;
$max = strlen ($contents);
while ( $pos < $max ) {
$bracket_open = strpos ($contents, '{', $pos);
if ( $bracket_open === false ) {
return false;
}
$bracket_close = strpos ($contents, '}', $pos);
if ( $bracket_close === false ) {
return false;
}
// If this is a nested call we might hit a closing } for the media section
// which was the reason for this function call. In this case break and return.
if ( $bracket_close < $bracket_open ) {
$pos = $bracket_close + 1;
break;
}
// Get the part before the open bracket and the last closing bracket
// (or the start of the string).
$before_open_bracket = substr ($contents, $pos, $bracket_open - $pos);
// Is it a @media rule?
$before_open_bracket = trim ($before_open_bracket);
$mediapos = stripos($before_open_bracket, '@media');
if ( $mediapos !== false ) {
// Yes, decode content as normal rules with @media ... { ... }
//$new_media = substr_replace ($before_open_bracket, NULL, $mediapos, strlen ('@media'));
$new_media = substr ($before_open_bracket, $mediapos + strlen ('@media'));
$contents_in_media = substr ($contents, $bracket_open + 1);
$nested_processed = 0;
$result = $this->importFromStringInternal ($contents_in_media, $new_media, $nested_processed);
if ( $result !== true ) {
// Stop parsing on error.
return false;
}
unset ($new_media);
$pos = $bracket_open + 1 + $nested_processed;
} else {
// No, decode rule the normal way selector { ... }
$selectors = explode (',', $before_open_bracket);
$decls = substr ($contents, $bracket_open + 1, $bracket_close - $bracket_open);
// Create a own, new rule for every selector
foreach ( $selectors as $selector ) {
$selector = trim ($selector);
$this->rules [] = new css_rule ($selector, $decls, $media);
}
$pos = $bracket_close + 1;
}
}
if ( $processed !== NULL ) {
$processed = $pos;
}
return true;
}
/**
* @param $filename
* @return bool|void
*/
function importFromFile($filename) {
// Try to read in the file content
if ( empty($filename) ) {
return false;
}
$handle = fopen($filename, "rb");
if ( $handle === false ) {
return false;
}
$contents = fread($handle, filesize($filename));
fclose($handle);
if ( $contents === false ) {
return false;
}
return $this->importFromString ($contents);
}
/**
* @param $filename
* @return bool
*/
function loadReplacements($filename) {
// Try to read in the file content
if ( empty($filename) ) {
return false;
}
$handle = fopen($filename, "rb");
if ( $handle === false ) {
return false;
}
$filesize = filesize($filename);
$contents = fread($handle, $filesize);
fclose($handle);
if ( $contents === false ) {
return false;
}
// Delete all comments first
$contents = preg_replace ('/;.*/', ' ', $contents);
// Find the start of the replacements section
$rep_start = strpos ($contents, '[replacements]');
if ( $rep_start === false ) {
return false;
}
$rep_start += strlen ('[replacements]');
// Find the end of the replacements section
// (The end is either the next section or the end of file)
$rep_end = strpos ($contents, '[', $rep_start);
if ( $rep_end === false ) {
$rep_end = $filesize - 1;
}
// Find all replacment definitions
$defs = substr ($contents, $rep_start, $rep_end - $rep_start);
$defs_end = strlen ($defs);
$def_pos = 0;
while ( $def_pos < $defs_end ) {
$linestart = strpos ($defs, "\n", $def_pos);
if ( $linestart === false ) {
break;
}
$linestart += strlen ("\n");
$lineend = strpos ($defs, "\n", $linestart);
if ( $lineend === false ) {
$lineend = $defs_end;
}
$equal_sign = strpos ($defs, '=', $linestart);
if ( $equal_sign === false || $equal_sign > $lineend ) {
$def_pos = $linestart;
continue;
}
$quote_start = strpos ($defs, '"', $equal_sign + 1);
if ( $quote_start === false || $quote_start > $lineend ) {
$def_pos = $linestart;
continue;
}
$quote_end = strpos ($defs, '"', $quote_start + 1);
if ( $quote_end === false || $quote_start > $lineend) {
$def_pos = $linestart;
continue;
}
if ( $quote_end - $quote_start < 2 ) {
$def_pos = $linestart;
continue;
}
$replacement = substr ($defs, $linestart, $equal_sign - $linestart);
$value = substr ($defs, $quote_start + 1, $quote_end - ($quote_start + 1));
$replacement = trim($replacement);
$value = trim($value);
$this->replacements [$replacement] = $value;
$def_pos = $lineend;
}
return true;
}
/**
* @return mixed
*/
public function getRaw () {
return $this->raw;
}
/**
* @param $name
* @return mixed
*/
public function getReplacement ($name) {
return $this->replacements [$name];
}
/**
* @param $element
* @param $classString
* @param $name
* @param null $media
* @return null
*/
public function getPropertyForElement ($element, $classString, $name, $media = NULL) {
if ( empty ($name) ) {
return NULL;
}
$value = NULL;
foreach ($this->rules as $rule) {
$matched = $rule->matches ($element, $classString, $media);
if ( $matched !== false ) {
$current = $rule->getProperty ($name);
if ( !empty ($current) ) {
$value = $current;
}
}
}
return $value;
}
/**
* @param $classString
* @param $name
* @return null
*/
public function getProperty ($classString, $name) {
if ( empty ($classString) || empty ($name) ) {
return NULL;
}
$value = $this->getPropertyForElement (NULL, $classString, $name);
return $value;
}
/**
* @param $dest
* @param $element
* @param $classString
* @param null $media
*/
public function getPropertiesForElement (&$dest, $element, $classString, $media = NULL, $cssId=NULL) {
if ( empty ($element) && empty ($classString) && empty ($cssId) ) {
return;
}
foreach ($this->rules as $rule) {
$matched = $rule->matches ($element, $classString, $media, $cssId);
if ( $matched !== false ) {
$rule->getProperties ($dest);
}
}
}
/**
* @param $value
* @param int $emValue
* @return string
*/
public function adjustValueForODT ($value, $emValue = 0) {
// ODT specific function. Shouldn't be used anymore.
// Call the ODT renderer's function instead.
dbg_deprecated('renderer_plugin_odt_page::adjustValueForODT');
$values = preg_split ('/\s+/', $value);
$value = '';
foreach ($values as $part) {
// Replace it if necessary
$part = trim($part);
$rep = $this->getReplacement($part);
if ( !empty ($rep) ) {
$part = $rep;
}
$length = strlen ($part);
// If it is a short color value (#xxx) then convert it to long value (#xxxxxx)
// (ODT does not support the short form)
if ( $part [0] == '#' && $length == 4 ) {
$part = '#'.$part [1].$part [1].$part [2].$part [2].$part [3].$part [3];
} else {
// If it is a CSS color name, get it's real color value
/** @var helper_plugin_odt_csscolors $odt_colors */
$odt_colors = plugin_load('helper', 'odt_csscolors');
$color = $odt_colors->getColorValue ($part);
if ( $part == 'black' || $color != '#000000' ) {
$part = $color;
}
}
if ( $length > 2 && $part [$length-2] == 'e' && $part [$length-1] == 'm' ) {
$number = substr ($part, 0, $length-2);
if ( is_numeric ($number) && !empty ($emValue) ) {
$part = ($number * $emValue).'pt';
}
}
// Replace px with pt (px does not seem to be supported by ODT)
if ( $length > 2 && $part [$length-2] == 'p' && $part [$length-1] == 'x' ) {
$part [$length-1] = 't';
}
$value .= ' '.$part;
}
$value = trim($value);
return $value;
}
/**
* @return string
*/
public function rulesToString () {
$returnString = '';
foreach ($this->rules as $rule) {
$returnString .= $rule->toString ();
}
return $returnString;
}
/**
* @param $URL
* @param $replacement
* @return string
*/
public function replaceURLPrefix ($URL, $replacement) {
if ( !empty ($URL) && !empty ($replacement) ) {
// Replace 'url(...)' with $replacement
$URL = substr ($URL, 3);
$URL = trim ($URL, '()');
$URL = $replacement.$URL;
}
return $URL;
}
/**
* @param $callback
*/
public function adjustLengthValues ($callback) {
foreach ($this->rules as $rule) {
$rule->adjustLengthValues ($callback);
}
}
}