1442 lines
51 KiB
PHP
1442 lines
51 KiB
PHP
![]() |
<?php
|
||
|
/**
|
||
|
* Class for importing and using CSS (new version).
|
||
|
* Partly uses code from the old version, e.g. css_declaration.
|
||
|
*
|
||
|
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||
|
* @author LarsDW223
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Class css_attribute_selector.
|
||
|
* Simple storage class to save exactly one CSS attribute selector.
|
||
|
*
|
||
|
* @package CSS\CSSAttributeSelector
|
||
|
*/
|
||
|
class css_attribute_selector {
|
||
|
/** var The namespace to which this attribute selector belongs */
|
||
|
protected $namespaze = NULL;
|
||
|
/** var The attribute name */
|
||
|
protected $attribute = NULL;
|
||
|
/** var The attribute selector operator */
|
||
|
protected $operator = NULL;
|
||
|
/** var The attribute selector value */
|
||
|
protected $value = NULL;
|
||
|
|
||
|
/**
|
||
|
* Construct the selector from $attribute_string.
|
||
|
*
|
||
|
* @param string $attribute_string String containing the selector
|
||
|
*/
|
||
|
public function __construct($attribute_string) {
|
||
|
$attribute_string = trim ($attribute_string, '[] ');
|
||
|
$found = strpos ($attribute_string, '|');
|
||
|
if ($found !== false &&
|
||
|
$attribute_string [$found+1] == '=') {
|
||
|
$found = strpos ($attribute_string, '|', $found+1);
|
||
|
}
|
||
|
if ($found !== false) {
|
||
|
if ($found > 0) {
|
||
|
$this->namespaze = substr ($attribute_string, 0, $found);
|
||
|
}
|
||
|
$attribute_string = substr ($attribute_string, $found + 1);
|
||
|
}
|
||
|
$found = strpos ($attribute_string, '=');
|
||
|
if ($found === false) {
|
||
|
$this->attribute = $attribute_string;
|
||
|
} else {
|
||
|
if (ctype_alpha($attribute_string [$found-1])) {
|
||
|
$this->attribute = substr($attribute_string, 0, $found);
|
||
|
$this->operator = '=';
|
||
|
$this->value = substr($attribute_string, $found + 1);
|
||
|
} else {
|
||
|
$this->attribute = substr($attribute_string, 0, $found - 1);
|
||
|
$this->operator = $attribute_string [$found-1].$attribute_string [$found];
|
||
|
$this->value = substr($attribute_string, $found + 1);
|
||
|
}
|
||
|
$this->value = trim ($this->value, '"');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function checks if this atrribute selector matches the
|
||
|
* attributes given in $attributes as key - value pairs.
|
||
|
*
|
||
|
* @param string $attributes String containing the selector
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function matches (array $attributes=NULL) {
|
||
|
if ($this->operator == NULL) {
|
||
|
// Attribute should be present
|
||
|
return isset($attributes) && array_key_exists($this->attribute, $attributes);
|
||
|
} else {
|
||
|
switch ($this->operator) {
|
||
|
case '=':
|
||
|
// Attribute should have exactly the value $this->value
|
||
|
if ($attributes [$this->attribute] == $this->value) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '~=':
|
||
|
// Attribute value should contain the word $this->value
|
||
|
$words = preg_split ('/\s/', $attributes [$this->attribute]);
|
||
|
if (array_search($this->value, $words) !== false) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '|=':
|
||
|
// Attribute value should contain the word $this->value
|
||
|
// or a word starting with $this->value.'-'
|
||
|
$with_hypen = $this->value.'-';
|
||
|
$length = strlen ($with_hypen);
|
||
|
if ($attributes [$this->attribute] == $this->value ||
|
||
|
strncmp($attributes [$this->attribute], $with_hypen, $length) == 0) {
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '^=':
|
||
|
// Attribute value should contain
|
||
|
// a word starting with $this->value
|
||
|
$length = strlen ($this->value);
|
||
|
if (strncmp($attributes [$this->attribute], $this->value, $length) == 0) {
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '$=':
|
||
|
// Attribute value should contain
|
||
|
// a word ending with $this->value
|
||
|
$length = -1 * strlen ($this->value);
|
||
|
if (substr($attributes [$this->attribute], $length) == $this->value) {
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '*=':
|
||
|
// Attribute value should include $this->value
|
||
|
if (strpos($attributes [$this->attribute], $this->value) !== false) {
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function returns a string representation of this attribute
|
||
|
* selector (only for debugging purpose).
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toString () {
|
||
|
$returnstring = '[';
|
||
|
if (!empty($this->namespaze)) {
|
||
|
$returnstring .= $this->namespaze.'|';
|
||
|
}
|
||
|
$returnstring .= $this->attribute.$this->operator.$this->value;
|
||
|
$returnstring .= ']';
|
||
|
return $returnstring;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class css_simple_selector
|
||
|
* Simple storage class to save a simple CSS selector.
|
||
|
*
|
||
|
* @package CSS\CSSSimpleSelector
|
||
|
*/
|
||
|
class css_simple_selector {
|
||
|
/** var Element name/Type of this simple selector */
|
||
|
protected $type = NULL;
|
||
|
/** var Pseudo element which this selector matches */
|
||
|
protected $pseudo_element = NULL;
|
||
|
/** var Id which this selector matches */
|
||
|
protected $id = NULL;
|
||
|
/** var Classes which this selector matches */
|
||
|
protected $classes = array();
|
||
|
/** var Pseudo classes which this selector matches */
|
||
|
protected $pseudo_classes = array();
|
||
|
/** var Attributes which this selector matches */
|
||
|
protected $attributes = array();
|
||
|
/** var Specificity of this selector */
|
||
|
protected $specificity = 0;
|
||
|
|
||
|
/**
|
||
|
* Internal function that checks if $sign is a sign that
|
||
|
* separates/identifies the different parts of an simple selector.
|
||
|
*
|
||
|
* @param character $sign
|
||
|
*/
|
||
|
protected function isSpecialSign ($sign) {
|
||
|
switch ($sign) {
|
||
|
case '.':
|
||
|
case '[':
|
||
|
case '#':
|
||
|
case ':':
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct the simple selector from $simple_selector_string.
|
||
|
*
|
||
|
* @param string $simple_selector_string String containing the selector
|
||
|
*/
|
||
|
public function __construct($simple_selector_string) {
|
||
|
$pos = 0;
|
||
|
$simple_selector_string = trim ($simple_selector_string);
|
||
|
$max = strlen ($simple_selector_string);
|
||
|
if ($max == 0) {
|
||
|
$this->type = '*';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$a = 0;
|
||
|
$b = 0;
|
||
|
$c = 0;
|
||
|
|
||
|
$content = '';
|
||
|
$first_sign = '';
|
||
|
$first = true;
|
||
|
$pseudo_element = false;
|
||
|
while ($pos < $max) {
|
||
|
$sign = $simple_selector_string [$pos];
|
||
|
if ($this->isSpecialSign ($sign)) {
|
||
|
if ($pos == 0) {
|
||
|
$first_sign = $sign;
|
||
|
} else {
|
||
|
// Found the end.
|
||
|
if (empty($first_sign)) {
|
||
|
// Element name/type
|
||
|
$this->type = $content;
|
||
|
if ($content != '*') {
|
||
|
$c++;
|
||
|
}
|
||
|
} else if ($first_sign == '.') {
|
||
|
// Class
|
||
|
$this->classes[] = $content;
|
||
|
$b++;
|
||
|
} else if ($first_sign == '#') {
|
||
|
// ID
|
||
|
$this->id = $content;
|
||
|
$a++;
|
||
|
} else if ($first_sign == ':') {
|
||
|
//if ($next_sign != ':') {
|
||
|
if (!$pseudo_element) {
|
||
|
// Pseudo class
|
||
|
$this->pseudo_classes[] = $content;
|
||
|
$b++;
|
||
|
} else {
|
||
|
// Pseudo element
|
||
|
$this->pseudo_element = $content;
|
||
|
$c++;
|
||
|
}
|
||
|
} else if ($first_sign == '[') {
|
||
|
$this->attributes [] = new css_attribute_selector($content);
|
||
|
$b++;
|
||
|
}
|
||
|
$first_sign = $sign;
|
||
|
$next_sign = $simple_selector_string [$pos+1];
|
||
|
if ($first_sign == ':' && $next_sign == ':') {
|
||
|
$pseudo_element = true;
|
||
|
$pos++;
|
||
|
} else {
|
||
|
$pseudo_element = false;
|
||
|
}
|
||
|
$content = '';
|
||
|
}
|
||
|
} else {
|
||
|
$content .= $sign;
|
||
|
}
|
||
|
$pos++;
|
||
|
}
|
||
|
|
||
|
// If $content is not empty then parse it
|
||
|
if (!empty($content)) {
|
||
|
if (empty($first_sign)) {
|
||
|
// Element name/type
|
||
|
$this->type = $content;
|
||
|
if ($content != '*') {
|
||
|
$c++;
|
||
|
}
|
||
|
} else if ($first_sign == '.') {
|
||
|
// Class
|
||
|
$this->classes[] = $content;
|
||
|
$b++;
|
||
|
} else if ($first_sign == '#') {
|
||
|
// ID
|
||
|
$this->id = $content;
|
||
|
$a++;
|
||
|
} else if ($first_sign == ':') {
|
||
|
if ($next_sign != ':') {
|
||
|
// Pseudo class
|
||
|
$this->pseudo_classes[] = $content;
|
||
|
$b++;
|
||
|
} else {
|
||
|
// Pseudo element
|
||
|
$this->pseudo_element = $content;
|
||
|
$c++;
|
||
|
}
|
||
|
} else if ($first_sign == '[') {
|
||
|
$this->attributes [] = new css_attribute_selector($content);
|
||
|
$b++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate specificity
|
||
|
$this->specificity = $a * 100 + $b *10 + $c;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The functions checks wheter this simple selector matches the given
|
||
|
* $element or not. $element must support the interface iElementCSSMatchable
|
||
|
* to enable this class to do the CSS selector matching.
|
||
|
*
|
||
|
* @param iElementCSSMatchable $element Element to check
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function matches_entry (iElementCSSMatchable $element) {
|
||
|
$element_attrs = $element->iECSSM_getAttributes();
|
||
|
|
||
|
// Match type/element
|
||
|
if (!empty($this->type) &&
|
||
|
$this->type != '*' &&
|
||
|
$this->type != $element->iECSSM_getName()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Match class(es)
|
||
|
if (count($this->classes) > 0) {
|
||
|
if (empty($element_attrs ['class'])) {
|
||
|
return false;
|
||
|
}
|
||
|
$comp = explode (' ', $element_attrs ['class']);
|
||
|
foreach ($this->classes as $search) {
|
||
|
if (array_search($search, $comp) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Match id
|
||
|
if (!empty($this->id) &&
|
||
|
$this->id != $element_attrs ['id']) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Match attributes
|
||
|
foreach ($this->attributes as $attr_sel) {
|
||
|
if ($attr_sel->matches ($element_attrs) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Match pseudo class(es)
|
||
|
if (count($this->pseudo_classes) > 0) {
|
||
|
foreach ($this->pseudo_classes as $search) {
|
||
|
if ($element->iECSSM_has_pseudo_class($search) == false) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Match pseudo element
|
||
|
if (!empty($this->pseudo_element)) {
|
||
|
if ($element->iECSSM_has_pseudo_element($this->pseudo_element) == false) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function returns a string representation of this simple
|
||
|
* selector (only for debugging purpose).
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toString () {
|
||
|
$returnstring = '';
|
||
|
if (!empty($this->type)) {
|
||
|
$returnstring .= $this->type;
|
||
|
}
|
||
|
if (!empty($this->id)) {
|
||
|
$returnstring .= '#'.$this->id;
|
||
|
}
|
||
|
foreach ($this->classes as $class) {
|
||
|
$returnstring .= '.'.$class;
|
||
|
}
|
||
|
foreach ($this->attributes as $attr_sel) {
|
||
|
$returnstring .= $attr_sel->toString();
|
||
|
}
|
||
|
return $returnstring;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the specificity of this simple selector.
|
||
|
*
|
||
|
* @return integer
|
||
|
*/
|
||
|
public function getSpecificity () {
|
||
|
return $this->specificity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class css_selector.
|
||
|
* Storage class to save a complete CSS selector.
|
||
|
* The class can also store multiple selectors, e.g. like 'h1 , h2, h3 {...}'
|
||
|
*
|
||
|
* @package CSS\CSSSelector
|
||
|
*/
|
||
|
class css_selector {
|
||
|
/** var Known combinators */
|
||
|
static protected $combinators = ' ,>+~';
|
||
|
/** var Brackets */
|
||
|
static protected $brackets = '[]';
|
||
|
/** var String from which this selector was created */
|
||
|
protected $selector_string = NULL;
|
||
|
/** var Array with parsed selector(s) */
|
||
|
protected $selectors_parsed = array();
|
||
|
/** var Specificity of this selector */
|
||
|
protected $specificity = array();
|
||
|
|
||
|
/**
|
||
|
* Construct the selector from $selector_string.
|
||
|
*
|
||
|
* @param string $selector_string String containing the selector
|
||
|
*/
|
||
|
public function __construct($selector_string) {
|
||
|
$selector_string = str_replace("\n", '', $selector_string);
|
||
|
$this->selector_string = trim($selector_string);
|
||
|
|
||
|
$pos = 0;
|
||
|
$max = strlen($this->selector_string);
|
||
|
$current = '';
|
||
|
$selector = array();
|
||
|
$specificity = 0;
|
||
|
$size = 0;
|
||
|
$in_brackets = false;
|
||
|
$separators = self::$combinators.self::$brackets;
|
||
|
while ($pos < $max) {
|
||
|
$sign = $this->selector_string [$pos];
|
||
|
$result = strpos ($separators, $sign);
|
||
|
if ($sign == '[') {
|
||
|
$in_brackets = true;
|
||
|
}
|
||
|
if ($result === false || $in_brackets == true) {
|
||
|
// No combinator
|
||
|
$current .= $sign;
|
||
|
$pos++;
|
||
|
|
||
|
if ($sign == ']') {
|
||
|
$in_brackets = false;
|
||
|
}
|
||
|
} else {
|
||
|
// Parse current
|
||
|
$selector [$size]['selector'] = new css_simple_selector($current);
|
||
|
$specificity += $selector [$size]['selector']->getSpecificity();
|
||
|
$size++;
|
||
|
$current = '';
|
||
|
|
||
|
$combinator = $sign;
|
||
|
$pos++;
|
||
|
while ($pos < $max) {
|
||
|
$sign = $this->selector_string[$pos];
|
||
|
if (strpos (self::$combinators, $sign) === false) {
|
||
|
break;
|
||
|
}
|
||
|
$combinator .= $sign;
|
||
|
$pos++;
|
||
|
}
|
||
|
if (ctype_space($combinator)) {
|
||
|
$selector [$size]['combinator'] = ' ';
|
||
|
$size++;
|
||
|
} else {
|
||
|
$combinator = trim ($combinator, ' ');
|
||
|
if ($combinator != ',') {
|
||
|
$selector [$size]['combinator'] = $combinator[0];
|
||
|
$size++;
|
||
|
} else {
|
||
|
$this->selectors_parsed [] = $selector;
|
||
|
$this->specificity [] = $specificity;
|
||
|
$selector = array();
|
||
|
$size = 0;
|
||
|
$specificity = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!empty($current)) {
|
||
|
$selector [$size]['selector'] = new css_simple_selector($current);
|
||
|
$specificity += $selector [$size]['selector']->getSpecificity();
|
||
|
$this->selectors_parsed [] = $selector;
|
||
|
$this->specificity [] = $specificity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function checks if the combined simple selectors in $selector
|
||
|
* match $element or not. $element must support the interface iElementCSSMatchable
|
||
|
* to enable this class to do the CSS selector matching.
|
||
|
*
|
||
|
* @param array $selector Internal selector array
|
||
|
* @param iElementCSSMatchable $element Element to check
|
||
|
* @return boolean
|
||
|
*/
|
||
|
protected function selector_matches (array $selector, iElementCSSMatchable $element) {
|
||
|
$combinator = '';
|
||
|
$found = 0;
|
||
|
$size = count($selector);
|
||
|
if ($size == 0 ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// First entry should be a selector
|
||
|
if ($selector [$size-1]['selector'] == NULL) {
|
||
|
// No! (Error)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Start comparison with the current element
|
||
|
$simple = $selector [$size-1]['selector'];
|
||
|
if ($simple->matches_entry ($element) == false) {
|
||
|
// If the current open element does not match then there is no match
|
||
|
return false;
|
||
|
}
|
||
|
if ($size == 1) {
|
||
|
// We are finished already
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Next entry should be a combinator
|
||
|
if ($selector [$size-2]['combinator'] == NULL) {
|
||
|
// No! (Error)
|
||
|
return false;
|
||
|
}
|
||
|
$combinator = $selector [$size-2]['combinator'];
|
||
|
|
||
|
$start_search = $element;
|
||
|
for ($index = $size-3 ; $index >= 0 ; $index--) {
|
||
|
// If we get here but start_search is already negative then there are
|
||
|
// selectors left but no more subjects/element to match.
|
||
|
if ($start_search < 0) {
|
||
|
return false;
|
||
|
}
|
||
|
if (empty($selector [$index]['combinator'])) {
|
||
|
$simple = $selector [$index]['selector'];
|
||
|
switch ($combinator) {
|
||
|
case ' ':
|
||
|
// Find any parent, parent's parent... that matches our simple selector
|
||
|
do {
|
||
|
$parent = $start_search->iECSSM_getParent();
|
||
|
if ($parent === NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $parent;
|
||
|
$is_match = $simple->matches_entry ($parent);
|
||
|
if ($is_match == true) {
|
||
|
// Found match. Stop this search.
|
||
|
break;
|
||
|
}
|
||
|
}while ($parent !== NULL);
|
||
|
|
||
|
// Did we find anything?
|
||
|
if (!$is_match) {
|
||
|
// No.
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $parent;
|
||
|
break;
|
||
|
|
||
|
case '>':
|
||
|
// Check if we have a parent and if it matches our simple selector
|
||
|
$parent = $start_search->iECSSM_getParent();
|
||
|
if ($parent === NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
if ($simple->matches_entry ($parent) == false) {
|
||
|
// No match.
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $parent;
|
||
|
break;
|
||
|
|
||
|
case '+':
|
||
|
// Immediate preceding sibling must match our simple selector
|
||
|
$sibling = $start_search->iECSSM_getPrecedingSibling();
|
||
|
if ($sibling === NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
if ($simple->matches_entry ($sibling) == false) {
|
||
|
// No match.
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $sibling;
|
||
|
break;
|
||
|
|
||
|
case '~':
|
||
|
// One of the preceding siblings must match our simple selector
|
||
|
do {
|
||
|
$sibling = $start_search->iECSSM_getPrecedingSibling();
|
||
|
if ($sibling === NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $sibling;
|
||
|
if ($simple->matches_entry ($sibling) == true) {
|
||
|
// Found match. Stop this search.
|
||
|
break;
|
||
|
}
|
||
|
}while ($sibling !== NULL);
|
||
|
|
||
|
// Did we find anything?
|
||
|
if ($sibling === NULL) {
|
||
|
// No.
|
||
|
return false;
|
||
|
}
|
||
|
$start_search = $sibling;
|
||
|
break;
|
||
|
|
||
|
// We won't get the combinator ',' here cause that is
|
||
|
// handled at construction time by creating an array of selectors
|
||
|
//case ',':
|
||
|
// break;
|
||
|
}
|
||
|
} else {
|
||
|
$combinator = $selector [$index]['combinator'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we get here then everything matches!
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The functions checks wheter any selector stored in this object
|
||
|
* match the given $element or not. $element must support the interface
|
||
|
* iElementCSSMatchable to enable this class to do the CSS selector matching.
|
||
|
*
|
||
|
* @param iElementCSSMatchable $element Element to check
|
||
|
* @param integer $specificity Specificity of matching selector
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function matches (iElementCSSMatchable $element, &$specificity) {
|
||
|
$size = count ($this->selectors_parsed);
|
||
|
$match = false;
|
||
|
$specificity = 0;
|
||
|
for ($index = 0 ; $index < $size ; $index++) {
|
||
|
if ($this->selector_matches ($this->selectors_parsed [$index], $element) == true) {
|
||
|
if ($this->specificity [$index] > $specificity) {
|
||
|
$specificity = $this->specificity [$index];
|
||
|
}
|
||
|
$match = true;
|
||
|
}
|
||
|
}
|
||
|
return $match;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function returns a string representation of this
|
||
|
* selector (only for debugging purpose).
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toString () {
|
||
|
$returnstring = '';
|
||
|
$max = count($this->selectors_parsed);
|
||
|
$index_parsed = 0;
|
||
|
foreach ($this->selectors_parsed as $selector) {
|
||
|
$size = count($selector);
|
||
|
for ($index = 0 ; $index < $size ; $index++) {
|
||
|
if ($selector [$index]['combinator'] !== NULL ) {
|
||
|
if ($selector [$index]['combinator'] == ' ') {
|
||
|
$returnstring .= ' ';
|
||
|
} else {
|
||
|
$returnstring .= ' '.$selector [$index]['combinator'].' ';
|
||
|
}
|
||
|
} else {
|
||
|
$simple = $selector [$index]['selector'];
|
||
|
$returnstring .= $simple->toString();
|
||
|
if ($index < $size-1) {
|
||
|
$returnstring .= ' ';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$index_parsed++;
|
||
|
if ($index_parsed < $max) {
|
||
|
$returnstring .= ',';
|
||
|
}
|
||
|
}
|
||
|
return $returnstring;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class css_rule_new.
|
||
|
*
|
||
|
* @package CSS\CSSRuleNew
|
||
|
*/
|
||
|
class css_rule_new {
|
||
|
/** @var Media selector to which this rule belongs */
|
||
|
protected $media = NULL;
|
||
|
/** @var Selector string from which this rule was created */
|
||
|
protected $selector = NULL;
|
||
|
/** @var Array of css_declaration objects */
|
||
|
protected $declarations = array ();
|
||
|
|
||
|
/**
|
||
|
* Construct rule from strings $selector and $decls.
|
||
|
*
|
||
|
* @param string $selector String containing the selector
|
||
|
* @param string $decls String containing the declarations
|
||
|
* @param string|null $media String containing the media selector
|
||
|
*/
|
||
|
public function __construct($selector, $decls, $media = NULL) {
|
||
|
|
||
|
$this->media = trim ($media);
|
||
|
//print ("\nNew rule: ".$media."\n"); //Debuging
|
||
|
|
||
|
// Create/parse selector
|
||
|
$this->selector = new css_selector ($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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function returns a string representation of this
|
||
|
* rule (only for debugging purpose).
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toString () {
|
||
|
$returnString = '';
|
||
|
$returnString .= "Media= \"".$this->media."\"\n";
|
||
|
$returnString .= $this->selector->toString().' ';
|
||
|
$returnString .= "{\n";
|
||
|
foreach ($this->declarations as $declaration) {
|
||
|
$returnString .= $declaration->getProperty ().':'.$declaration->getValue ().";\n";
|
||
|
}
|
||
|
$returnString .= "}\n";
|
||
|
return $returnString;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The functions checks wheter this rule matches the given $element
|
||
|
* or not. $element must support the interface iElementCSSMatchable
|
||
|
* to enable this class to do the CSS selector matching.
|
||
|
*
|
||
|
* @param iElementCSSMatchable $element Element to check
|
||
|
* @param integer $specificity Specificity of matching selector
|
||
|
* @param string $media Media selector to match
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function matches(iElementCSSMatchable $element, &$specificity, $media = NULL) {
|
||
|
$media = trim($media);
|
||
|
if ( !empty($this->media) && $media !== $this->media ) {
|
||
|
// Wrong media
|
||
|
//print ("\nNo-Match ".$this->media."==".$media); //Debuging
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// The rules does match if the selector does match
|
||
|
$result = $this->selector->matches($element, $specificity);
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function returns the value of property $name or null if a
|
||
|
* property with that name does not exist in this rule.
|
||
|
*
|
||
|
* @param string $name The property name
|
||
|
* @return string|null
|
||
|
*/
|
||
|
public function getProperty ($name) {
|
||
|
foreach ($this->declarations as $declaration) {
|
||
|
if ( $name == $declaration->getProperty () ) {
|
||
|
return $declaration->getValue ();
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function stores all properties of this rule in the array
|
||
|
* $values as key - value pairs, e.g. $values ['color'] = 'red';
|
||
|
*
|
||
|
* @param array $values Array for property storage
|
||
|
* @return null
|
||
|
*/
|
||
|
public function getProperties (&$values) {
|
||
|
foreach ($this->declarations as $declaration) {
|
||
|
$property = $declaration->getProperty ();
|
||
|
$value = $declaration->getValue ();
|
||
|
$values [$property] = $value;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function calls $callback for each property stored in this
|
||
|
* rule containing a length value. The return value of $callback
|
||
|
* is saved as the new property value.
|
||
|
*
|
||
|
* @param callable $callback
|
||
|
*/
|
||
|
public function adjustLengthValues ($callback) {
|
||
|
foreach ($this->declarations as $declaration) {
|
||
|
$declaration->adjustLengthValues ($callback, $this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function calls $callback for each property stored in this
|
||
|
* rule containing a URL reference. The return value of $callback
|
||
|
* is saved as the new property value.
|
||
|
*
|
||
|
* @param callable $callback
|
||
|
*/
|
||
|
public function replaceURLPrefixes ($callback) {
|
||
|
foreach ($this->declarations as $declaration) {
|
||
|
$declaration->replaceURLPrefixes ($callback);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class cssimportnew
|
||
|
*
|
||
|
* @package CSS\CSSImportNew
|
||
|
*/
|
||
|
class cssimportnew {
|
||
|
/** var Imported raw CSS code */
|
||
|
protected $raw;
|
||
|
/** @var Array of css_rule_new */
|
||
|
protected $rules = array ();
|
||
|
/** @var Actually set media selector */
|
||
|
protected $media = NULL;
|
||
|
|
||
|
/**
|
||
|
* Import CSS code from string $contents.
|
||
|
* Returns true on success or false if any error occured during CSS parsing.
|
||
|
*
|
||
|
* @param string $contents
|
||
|
* @return boolean
|
||
|
*/
|
||
|
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++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the media selector to use for CSS matching to $media.
|
||
|
*
|
||
|
* @param string $media
|
||
|
*/
|
||
|
public function setMedia($media) {
|
||
|
$this->media = $media;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the actually set media selector.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getMedia() {
|
||
|
return $this->media;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal function that imports CSS code from string $contents.
|
||
|
* (The function is calling itself recursively)
|
||
|
*
|
||
|
* @param string $contents
|
||
|
* @param string|null $media Actually valid media selector
|
||
|
* @param integer $processed Position to which $contents were parsed
|
||
|
* @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 @something rule?
|
||
|
$before_open_bracket = trim ($before_open_bracket);
|
||
|
$at_rule_pos = stripos($before_open_bracket, '@');
|
||
|
if ( $at_rule_pos !== false ) {
|
||
|
$at_rule_end = stripos($before_open_bracket, ' ');
|
||
|
|
||
|
// Yes, decode content as normal rules with @something ... { ... }
|
||
|
$at_rule_name = substr ($before_open_bracket, $at_rule_pos, $at_rule_end - $at_rule_pos);
|
||
|
if ($at_rule_name == '@media') {
|
||
|
$at_rule_name = substr ($before_open_bracket, $at_rule_end);
|
||
|
}
|
||
|
$contents_in_media = substr ($contents, $bracket_open + 1);
|
||
|
|
||
|
$nested_processed = 0;
|
||
|
$result = $this->importFromStringInternal ($contents_in_media, $at_rule_name, $nested_processed);
|
||
|
if ( $result !== true ) {
|
||
|
// Stop parsing on error.
|
||
|
return false;
|
||
|
}
|
||
|
unset ($at_rule_name);
|
||
|
$pos = $bracket_open + 1 + $nested_processed;
|
||
|
} else {
|
||
|
|
||
|
// No, decode rule the normal way selector { ... }
|
||
|
// The selector is stored in $before_open_bracket
|
||
|
$decls = substr ($contents, $bracket_open + 1, $bracket_close - $bracket_open);
|
||
|
$this->rules [] = new css_rule_new ($before_open_bracket, $decls, $media);
|
||
|
|
||
|
$pos = $bracket_close + 1;
|
||
|
}
|
||
|
}
|
||
|
if ( $processed !== NULL ) {
|
||
|
$processed = $pos;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import CSS code from file filename.
|
||
|
* Returns true on success or false if any error occured during CSS parsing.
|
||
|
*
|
||
|
* @param string $filename
|
||
|
* @return boolean
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the original CSS code that was imported.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getRaw () {
|
||
|
return $this->raw;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the value of CSS property for element $element.
|
||
|
* If $element is not matched by any rule or the rule(s) matching
|
||
|
* do not contain the property $name then null is returned.
|
||
|
*
|
||
|
* @param string $name Name of queried property
|
||
|
* @param iElementCSSMatchable $element Element to match
|
||
|
* @return string|null
|
||
|
*/
|
||
|
public function getPropertyForElement ($name, iElementCSSMatchable $element) {
|
||
|
if ( empty ($name) ) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
$value = NULL;
|
||
|
$highest = 0;
|
||
|
foreach ($this->rules as $rule) {
|
||
|
$matched = $rule->matches($element, $specificity, $this->media);
|
||
|
if ( $matched !== false ) {
|
||
|
$current = $rule->getProperty ($name);
|
||
|
|
||
|
// Only accept the property value if the current specificity of the matched
|
||
|
// rule/selector is higher or equal than the highest one.
|
||
|
if ( !empty ($current) && $specificity >= $highest) {
|
||
|
$highest = $specificity;
|
||
|
$value = $current;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get all properties for element $element and store them in $dest.
|
||
|
* Properties are stored as key -value pairs, e.g. $dest ['color'] = 'red';
|
||
|
* If $element is not matched by any rule then array $dest will be
|
||
|
* empty (if it was empty before the call!).
|
||
|
*
|
||
|
* @param array $dest Property storage
|
||
|
* @param iElementCSSMatchable $element Element to match
|
||
|
* @param ODTUnits $units ODTUnits object for conversion
|
||
|
* @param boolean $inherit Enable/disable inheritance
|
||
|
* @return string|null
|
||
|
*/
|
||
|
public function getPropertiesForElement (&$dest, iElementCSSMatchable $element, ODTUnits $units, $inherit=true) {
|
||
|
if ($element == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$highest = array();
|
||
|
$temp = array();
|
||
|
foreach ($this->rules as $rule) {
|
||
|
$matched = $rule->matches ($element, $specificity, $this->media);
|
||
|
if ( $matched !== false ) {
|
||
|
$current = array();
|
||
|
$rule->getProperties ($current);
|
||
|
|
||
|
// Only accept a property value if the current specificity of the matched
|
||
|
// rule/selector is higher or equal than the highest one.
|
||
|
foreach ($current as $property => $value) {
|
||
|
if ($specificity >= $highest [$property]) {
|
||
|
$highest [$property] = $specificity;
|
||
|
$temp [$property] = $value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add inline style properties if present (always have highest specificity):
|
||
|
// Create rule with selector '*' (doesn't matter) and inline style declarations
|
||
|
$attributes = $element->iECSSM_getAttributes();
|
||
|
if (!empty($attributes ['style'])) {
|
||
|
$rule = new css_rule ('*', $attributes ['style']);
|
||
|
$rule->getProperties ($temp);
|
||
|
}
|
||
|
|
||
|
if ($inherit) {
|
||
|
// Now calculate absolute values and inherit values from parents
|
||
|
$this->calculateAndInherit ($temp, $element, $units);
|
||
|
unset($temp ['calculated']);
|
||
|
}
|
||
|
|
||
|
$dest = $temp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the value of CSS property for element $parent. If $parent has
|
||
|
* no match for the property with name $key then return the value of
|
||
|
* the property for $parent's parents.
|
||
|
*
|
||
|
* @param string $key Name of queried property
|
||
|
* @param iElementCSSMatchable $parent Element to match
|
||
|
* @return string|null
|
||
|
*/
|
||
|
protected function getParentsValue($key, iElementCSSMatchable $parent) {
|
||
|
$properties = $parent->getProperties ();
|
||
|
if ($properties [$key] != NULL) {
|
||
|
return $properties [$key];
|
||
|
}
|
||
|
|
||
|
$parentsParent = $parent->iECSSM_getParent();
|
||
|
if ($parentsParent != NULL) {
|
||
|
return $this->getParentsValue($key, $parentsParent);
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function calculates the absolute values for the relative
|
||
|
* property values of element $element and store them in $properties.
|
||
|
*
|
||
|
* @param array $properties Property storage
|
||
|
* @param iElementCSSMatchable $element Element to match
|
||
|
* @param ODTUnits $units ODTUnits object for conversion
|
||
|
*/
|
||
|
protected function calculate (array &$properties, iElementCSSMatchable $element, ODTUnits $units) {
|
||
|
if ($properties ['calculated'] == '1') {
|
||
|
// Already done
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$properties ['calculated'] = '1';
|
||
|
$parent = $element->iECSSM_getParent();
|
||
|
|
||
|
// First get absolute font-size in points for
|
||
|
// conversion of relative units
|
||
|
if ($parent != NULL) {
|
||
|
$font_size = $this->getParentsValue('font-size', $parent);
|
||
|
}
|
||
|
if ($font_size != NULL) {
|
||
|
// Use the parents value
|
||
|
// (It is assumed that the value is already calculated to an absolute
|
||
|
// value. That's why the loops in calculateAndInherit() must run backwards
|
||
|
$base_font_size_in_pt = $units->getDigits($font_size);
|
||
|
} else {
|
||
|
// If there is no parent value use global setting
|
||
|
$base_font_size_in_pt = $units->getPixelPerEm ().'px';
|
||
|
$base_font_size_in_pt = $units->toPoints($base_font_size_in_pt, 'y');
|
||
|
$base_font_size_in_pt = $units->getDigits($base_font_size_in_pt);
|
||
|
}
|
||
|
|
||
|
// Do we have font-size or line-height set?
|
||
|
if ($properties ['font-size'] != NULL || $properties ['line-height'] != NULL) {
|
||
|
if ($properties ['font-size'] != NULL) {
|
||
|
$font_size_unit = $units->stripDigits($properties ['font-size']);
|
||
|
$font_size_digits = $units->getDigits($properties ['font-size']);
|
||
|
if ($font_size_unit == '%' || $font_size_unit == 'em') {
|
||
|
$base_font_size_in_pt = $units->getAbsoluteValue ($properties ['font-size'], $base_font_size_in_pt);
|
||
|
$properties ['font-size'] = $base_font_size_in_pt.'pt';
|
||
|
|
||
|
} elseif ($font_size_unit != 'pt') {
|
||
|
$properties ['font-size'] = $units->toPoints($properties ['font-size'], 'y');
|
||
|
$base_font_size_in_pt = $units->getDigits($properties ['font-size']);
|
||
|
} else {
|
||
|
$base_font_size_in_pt = $units->getDigits($properties ['font-size']);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert relative line-heights to absolute
|
||
|
if ($properties ['line-height'] != NULL) {
|
||
|
$line_height_unit = $units->stripDigits($properties ['line-height']);
|
||
|
$line_height_digits = $units->getDigits($properties ['line-height']);
|
||
|
if ($line_height_unit == '%') {
|
||
|
$properties ['line-height'] = (($line_height_digits * $base_font_size_in_pt)/100).'pt';
|
||
|
} elseif (empty($line_height_unit)) {
|
||
|
$properties ['line-height'] = ($line_height_digits * $base_font_size_in_pt).'pt';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate all other absolute values
|
||
|
// (NOT 'width' as it depends on the encapsulating element,
|
||
|
// and not 'font-size' and 'line-height' => already done above
|
||
|
foreach ($properties as $key => $value) {
|
||
|
switch ($key) {
|
||
|
case 'width':
|
||
|
case 'font-size':
|
||
|
case 'line-height':
|
||
|
// Do nothing.
|
||
|
break;
|
||
|
case 'margin':
|
||
|
case 'margin-left':
|
||
|
case 'margin-right':
|
||
|
case 'margin-top':
|
||
|
case 'margin-bottom':
|
||
|
// Do nothing.
|
||
|
// We do not know the size of the surrounding element.
|
||
|
break;
|
||
|
default:
|
||
|
// Convert '%' or 'em' value based on determined font-size
|
||
|
$unit = $units->stripDigits($value);
|
||
|
if ($unit == '%' || $unit == 'em') {
|
||
|
$value = $units->getAbsoluteValue ($value, $base_font_size_in_pt);
|
||
|
$properties [$key] = $value.'pt';
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$element->setProperties($properties);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function inherits all properties of the $parents into array
|
||
|
* $dest. $parents is an array of elements (iElementCSSMatchable).
|
||
|
*
|
||
|
* @param array $dest Property storage
|
||
|
* @param array $parents Parents to inherit from
|
||
|
*/
|
||
|
protected function inherit (array &$dest, array $parents) {
|
||
|
// Inherit properties of all parents
|
||
|
// (MUST be done backwards!)
|
||
|
$max = count ($parents);
|
||
|
foreach ($parents as $parent) {
|
||
|
$properties = $parent->getProperties ();
|
||
|
foreach ($properties as $key => $value) {
|
||
|
if ($dest [$key] == 'inherit') {
|
||
|
$dest [$key] = $value;
|
||
|
} else {
|
||
|
if (strncmp($key, 'background', strlen('background')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'border', strlen('border')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'padding', strlen('padding')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'margin', strlen('margin')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'outline', strlen('outline')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'counter', strlen('counter')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'page-break', strlen('page-break')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'cue', strlen('cue')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strncmp($key, 'pause', strlen('pause')) == 0) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strpos($key, 'width') !== false) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
if (strpos($key, 'height') !== false) {
|
||
|
// The property may not be inherited
|
||
|
continue;
|
||
|
}
|
||
|
switch ($key) {
|
||
|
case 'text-decoration':
|
||
|
case 'text-shadow':
|
||
|
case 'display':
|
||
|
case 'table-layout':
|
||
|
case 'vertical-align':
|
||
|
case 'visibility':
|
||
|
case 'position':
|
||
|
case 'top':
|
||
|
case 'right':
|
||
|
case 'bottom':
|
||
|
case 'left':
|
||
|
case 'float':
|
||
|
case 'clear':
|
||
|
case 'z-index':
|
||
|
case 'unicode-bidi':
|
||
|
case 'overflow':
|
||
|
case 'clip':
|
||
|
case 'visibility':
|
||
|
case 'content':
|
||
|
case 'marker-offset':
|
||
|
case 'play-during':
|
||
|
// The property may not be inherited
|
||
|
break;
|
||
|
default:
|
||
|
if ($dest [$key] == NULL || $dest [$key] == 'inherit') {
|
||
|
$dest [$key] = $value;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Main function performing calculation and inheritance for element
|
||
|
* $element. Properties are stored in $dest.
|
||
|
*
|
||
|
* @param array $dest Property storage
|
||
|
* @param array $element Element to match
|
||
|
* @param ODTUnits $units ODTUnits object for conversion
|
||
|
*/
|
||
|
protected function calculateAndInherit (array &$dest, iElementCSSMatchable $element, ODTUnits $units) {
|
||
|
$parents = array();
|
||
|
$parent = $element->iECSSM_getParent();
|
||
|
while ($parent != NULL) {
|
||
|
$parents [] = $parent;
|
||
|
$parent = $parent->iECSSM_getParent();
|
||
|
}
|
||
|
|
||
|
// Determine properties of all parents if not done yet
|
||
|
// and calculate absolute values
|
||
|
// (MUST be done backwards!)
|
||
|
$max = count ($parents);
|
||
|
for ($index = $max-1 ; $index >= 0 ; $index--) {
|
||
|
$properties = $parents [$index]->getProperties ();
|
||
|
if ($properties == NULL) {
|
||
|
$properties = array();
|
||
|
$this->getPropertiesForElement ($properties, $parents [$index], $units, false);
|
||
|
$parents [$index]->setProperties ($properties);
|
||
|
}
|
||
|
if ($properties ['calculated'] == NULL) {
|
||
|
$this->calculate($properties, $parents [$index], $units);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate our own absolute values
|
||
|
$this->calculate($dest, $element, $units);
|
||
|
|
||
|
// Inherit values from our parents
|
||
|
$this->inherit($dest, $parents);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a string representation of all imported rules.
|
||
|
* (String can be large)
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function rulesToString () {
|
||
|
$returnString = '';
|
||
|
foreach ($this->rules as $rule) {
|
||
|
$returnString .= $rule->toString ();
|
||
|
}
|
||
|
return $returnString;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function strips the 'url(...)' part from an URL reference
|
||
|
* and puts a $replacement path in front of the rest.
|
||
|
*
|
||
|
* @param string $URL Original URL reference
|
||
|
* @param string $replacement Replacement path to set
|
||
|
* @return string
|
||
|
*/
|
||
|
public static 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function calls $callback for each imported property
|
||
|
* containing a length value. The return value of $callback
|
||
|
* is saved as the new property value.
|
||
|
*
|
||
|
* @param callable $callback
|
||
|
*/
|
||
|
public function adjustLengthValues ($callback) {
|
||
|
foreach ($this->rules as $rule) {
|
||
|
$rule->adjustLengthValues ($callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The function calls $callback for each property imported
|
||
|
* containing a URL reference. The return value of $callback
|
||
|
* is saved as the new property value.
|
||
|
*
|
||
|
* @param callable $callback
|
||
|
*/
|
||
|
public function replaceURLPrefixes ($callback) {
|
||
|
foreach ($this->rules as $rule) {
|
||
|
$rule->replaceURLPrefixes ($callback);
|
||
|
}
|
||
|
}
|
||
|
}
|