| <?php |
| /** |
| * File containing the ezcDocumentPcssStyleInferencer class |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Document |
| * @version //autogen// |
| * @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved. |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| * @access private |
| */ |
| |
| /** |
| * Style inferencer |
| * |
| * Inferences the style of a element, basing on the default styles for the |
| * element and the given list of user defined style directives. |
| * |
| * This class is meant to return a list of styles for any element in the |
| * Docbook document tree. To speed up the inferencing process styles for |
| * elements with the same path are cached. |
| * |
| * The inferencing algorithm basically works like: |
| * |
| * 1) Apply the default styles to the element |
| * 2) Inherit styles from the parent element |
| * 3) Apply styles from all given style directives in their given order, so |
| * that rules defined later overwrite rules defined earlier. |
| * |
| * @package Document |
| * @access private |
| * @version //autogen// |
| */ |
| class ezcDocumentPcssStyleInferencer |
| { |
| /** |
| * Style cache |
| * |
| * Caches styles for defined paths. This speeds up resolving of styles for |
| * similar or same elements multiple times. |
| * |
| * @var array |
| */ |
| protected $styleCache = array(); |
| |
| /** |
| * Ordered list of style directives |
| * |
| * Ordered list of style directvies, which each include the pattern, and a |
| * list of formatting rules. Matching directives are applied in the given |
| * order and may overwrite each other. |
| * |
| * @var array |
| */ |
| protected $styleDirectives = array(); |
| |
| /** |
| * Special classes for style directive values |
| * |
| * If no class is given it will fall back to a generic string value. |
| * |
| * @var array |
| */ |
| protected $valueParserClasses = array( |
| 'font-size' => 'ezcDocumentPcssStyleMeasureValue', |
| 'line-height' => 'ezcDocumentPcssStyleMeasureValue', |
| 'margin' => 'ezcDocumentPcssStyleMeasureBoxValue', |
| 'margin-top' => 'ezcDocumentPcssStyleMeasureValue', |
| 'margin-right' => 'ezcDocumentPcssStyleMeasureValue', |
| 'margin-bottom' => 'ezcDocumentPcssStyleMeasureValue', |
| 'margin-left' => 'ezcDocumentPcssStyleMeasureValue', |
| 'padding' => 'ezcDocumentPcssStyleMeasureBoxValue', |
| 'padding-top' => 'ezcDocumentPcssStyleMeasureValue', |
| 'padding-right' => 'ezcDocumentPcssStyleMeasureValue', |
| 'padding-bottom' => 'ezcDocumentPcssStyleMeasureValue', |
| 'padding-left' => 'ezcDocumentPcssStyleMeasureValue', |
| 'text-columns' => 'ezcDocumentPcssStyleIntValue', |
| 'text-columns-spacing' => 'ezcDocumentPcssStyleMeasureValue', |
| 'text-decoration' => 'ezcDocumentPcssStyleListValue', |
| 'color' => 'ezcDocumentPcssStyleColorValue', |
| 'background-color' => 'ezcDocumentPcssStyleColorValue', |
| 'border' => 'ezcDocumentPcssStyleBorderBoxValue', |
| 'border-top' => 'ezcDocumentPcssStyleBorderValue', |
| 'border-right' => 'ezcDocumentPcssStyleBorderValue', |
| 'border-bottom' => 'ezcDocumentPcssStyleBorderValue', |
| 'border-left' => 'ezcDocumentPcssStyleBorderValue', |
| 'border-style' => 'ezcDocumentPcssStyleLineBoxValue', |
| 'border-style-top' => 'ezcDocumentPcssStyleLineValue', |
| 'border-style-right' => 'ezcDocumentPcssStyleLineValue', |
| 'border-style-bottom' => 'ezcDocumentPcssStyleLineValue', |
| 'border-style-left' => 'ezcDocumentPcssStyleLineValue', |
| 'border-color' => 'ezcDocumentPcssStyleColorBoxValue', |
| 'border-color-top' => 'ezcDocumentPcssStyleColorValue', |
| 'border-color-right' => 'ezcDocumentPcssStyleColorValue', |
| 'border-color-bottom' => 'ezcDocumentPcssStyleColorValue', |
| 'border-color-left' => 'ezcDocumentPcssStyleColorValue', |
| 'border-width' => 'ezcDocumentPcssStyleMeasureBoxValue', |
| 'border-width-top' => 'ezcDocumentPcssStyleMeasureValue', |
| 'border-width-right' => 'ezcDocumentPcssStyleMeasureValue', |
| 'border-width-bottom' => 'ezcDocumentPcssStyleMeasureValue', |
| 'border-width-left' => 'ezcDocumentPcssStyleMeasureValue', |
| 'src' => 'ezcDocumentPcssStyleSrcValue', |
| ); |
| |
| /** |
| * Text category of style directives |
| */ |
| const TEXT = 1; |
| |
| /** |
| * Layout category of style directives |
| */ |
| const LAYOUT = 2; |
| |
| /** |
| * Page category of style directives |
| */ |
| const PAGE = 4; |
| |
| /** |
| * CSS property categories, used to minimize the amount of returned |
| * properties |
| * |
| * @var array |
| */ |
| protected $categories = array( |
| self::TEXT => array( |
| 'direction', |
| 'text-decoration', |
| 'text-align', |
| 'font-size', |
| 'font-family', |
| 'font-weight', |
| 'font-style', |
| 'background-color', |
| 'line-height', |
| 'color', |
| ), |
| self::LAYOUT => array( |
| 'line-height', |
| 'text-columns', |
| 'text-column-spacing', |
| 'margin', |
| 'padding', |
| 'orphans', |
| 'widows', |
| ), |
| self::PAGE => array( |
| 'page-size', |
| 'page-orientation', |
| 'margin', |
| 'padding', |
| ), |
| ); |
| |
| /** |
| * Construct style inference with default styles |
| * |
| * @param bool $loadDefault |
| * @return void |
| */ |
| public function __construct( $loadDefault = true ) |
| { |
| if ( $loadDefault ) |
| { |
| $this->loadDefaultStyles(); |
| } |
| } |
| |
| /** |
| * Set the default styles |
| * |
| * Creates a list of default styles for very common elements. |
| * |
| * @return void |
| */ |
| protected function loadDefaultStyles() |
| { |
| if ( file_exists( $file = dirname( __FILE__ ) . '/style/default.php' ) ) |
| { |
| $this->appendStyleDirectives( include $file ); |
| return; |
| } |
| |
| // If the file does not exist parse the PCSS style file |
| $parser = new ezcDocumentPcssParser(); |
| $directives = $parser->parseFile( dirname( __FILE__ ) . '/style/default.css' ); |
| |
| // Write parsed object tree back to file |
| file_put_contents( $file, "<?php\n\nreturn " . str_replace( dirname( __FILE__ ) . '/', '', var_export( $directives, true ) ) . ";\n\n?>" ); // */ |
| |
| $this->appendStyleDirectives( $directives ); |
| } |
| |
| /** |
| * Append list of style directives |
| * |
| * Append another set of style directives. Since style directives are |
| * applied in the given order and may overwrite each other, all given |
| * directives might overwrite existing formatting rules. |
| * |
| * @param array $styleDirectives |
| * @return void |
| */ |
| public function appendStyleDirectives( array $styleDirectives ) |
| { |
| // Convert values, depending on assigned value handler classes |
| foreach ( $styleDirectives as $nr => $directive ) |
| { |
| foreach ( $directive->formats as $name => $value ) |
| { |
| try |
| { |
| $valueHandler = isset( $this->valueParserClasses[$name] ) ? $this->valueParserClasses[$name] : 'ezcDocumentPcssStyleStringValue'; |
| $styleDirectives[$nr]->formats[$name] = new $valueHandler(); |
| $styleDirectives[$nr]->formats[$name]->parse( $value ); |
| } |
| catch ( ezcDocumentParserException $e ) |
| { |
| // Annotate parser exceptions with additional information |
| // from directive context |
| throw new ezcDocumentParserException( |
| E_PARSE, $e->parseError, |
| $directive->file, $directive->line, $directive->position, |
| $e |
| ); |
| } |
| } |
| } |
| |
| $this->styleDirectives = array_merge( |
| $this->styleDirectives, |
| $styleDirectives |
| ); |
| |
| // Clear styl cache, since new styles may leed to different results |
| $this->styleCache = array(); |
| } |
| |
| /** |
| * Merges box values into one single definition |
| * |
| * Merges partial box definitions, like "margin-top", into a single |
| * "margin" definition, so it can be access easier. |
| * |
| * @param array $values |
| * @return array |
| */ |
| protected function mergeBoxValues( array $values ) |
| { |
| $merged = array(); |
| |
| foreach ( $values as $name => $value ) |
| { |
| if ( ( strpos( $name, '-' . ( $position = 'top' ) ) !== false ) || |
| ( strpos( $name, '-' . ( $position = 'right' ) ) !== false ) || |
| ( strpos( $name, '-' . ( $position = 'bottom' ) ) !== false ) || |
| ( strpos( $name, '-' . ( $position = 'left' ) ) !== false ) ) |
| { |
| $baseProperty = substr( $name, 0, -( strlen( $position ) + 1 ) ); |
| |
| if ( isset( $merged[$baseProperty] ) ) |
| { |
| $merged[$baseProperty]->value[$position] = $value->value; |
| } |
| elseif ( isset( $this->valueParserClasses[$baseProperty] ) ) |
| { |
| $class = $this->valueParserClasses[$baseProperty]; |
| $merged[$baseProperty] = new $class(); |
| $merged[$baseProperty]->value[$position] = $value->value; |
| } |
| else |
| { |
| throw new ezcDocumentParserException( "Unknown property to merge: $baseProperty." ); |
| } |
| } |
| else |
| { |
| $merged[$name] = clone $value; |
| } |
| } |
| |
| return $merged; |
| } |
| |
| /** |
| * Merges border values into one single definition |
| * |
| * Merges partial border definitions like "border-style" into a single |
| * border definition, which then includes all border properties. |
| * |
| * @param array $values |
| * @return array |
| */ |
| protected function mergeBorderValues( array $values ) |
| { |
| $merged = array(); |
| |
| foreach ( $values as $name => $value ) |
| { |
| if ( ( $name === 'border-' . ( $type = 'width' ) ) || |
| ( $name === 'border-' . ( $type = 'style' ) ) || |
| ( $name === 'border-' . ( $type = 'color' ) ) ) |
| { |
| if ( !isset( $merged['border'] ) ) |
| { |
| $merged['border'] = new ezcDocumentPcssStyleBorderBoxValue(); |
| } |
| |
| foreach ( $merged['border']->value as $position => $dummy ) |
| { |
| if ( $value->value[$position] !== null ) |
| { |
| $merged['border']->value[$position][$type] = $value->value[$position]; |
| } |
| } |
| } |
| else |
| { |
| $merged[$name] = clone $value; |
| } |
| } |
| |
| return $merged; |
| } |
| |
| /** |
| * Merge formatting rules |
| * |
| * Merges two sets of formatting rules, while rules set in the second rule |
| * set will always overwrite existing rules of the same name in the first. |
| * Rules in the first set, not existing in the second will left untouched. |
| * |
| * @param array $base |
| * @param array $new |
| * @return array |
| */ |
| protected function mergeFormattingRules( array $base, array $new ) |
| { |
| foreach ( $new as $k => $v ) |
| { |
| // Unset key first, to keep the array order intact. |
| if ( isset( $base[$k] ) ) |
| { |
| unset( $base[$k] ); |
| } |
| |
| $base[$k] = $v; |
| } |
| |
| $base = $this->mergeBoxValues( $base ); |
| $base = $this->mergeBorderValues( $base ); |
| return $base; |
| } |
| |
| /** |
| * Filter the styles |
| * |
| * Filter the styles so that only styles of the specified classes are |
| * returned. |
| * |
| * @param array $formats |
| * @param int $types |
| * @return array void |
| */ |
| protected function filterStyles( array $formats, $types ) |
| { |
| if ( $types === -1 ) |
| { |
| return $formats; |
| } |
| |
| // Filter formats to only include formats matching the given category |
| $filtered = array(); |
| foreach ( $this->categories as $type => $properties ) |
| { |
| if ( !( $type & $types ) ) |
| { |
| continue; |
| } |
| |
| foreach ( $formats as $name => $value ) |
| { |
| if ( in_array( $name, $properties, true ) ) |
| { |
| $filtered[$name] = $value; |
| unset( $formats[$name] ); |
| } |
| } |
| } |
| |
| return $filtered; |
| } |
| |
| /** |
| * Inference formatting rules for element |
| * |
| * Inference the formatting rules for the passed DOMElement or location id. |
| * First the cache will be checked for already inferenced formatting rules |
| * defined for this element type, using its generated location identifier. |
| * |
| * Of not cached, the formatting rules will be inferenced using the |
| * algorithm described in the class header. |
| * |
| * @param ezcDocumentLocateable $element |
| * @param int $types |
| * @return array |
| */ |
| public function inferenceFormattingRules( ezcDocumentLocateable $element, $types = -1 ) |
| { |
| // Check style cache early, to speed things up. |
| $locationId = $element->getLocationId(); |
| if ( isset( $this->styleCache[$locationId] ) ) |
| { |
| return $this->filterStyles( $this->styleCache[$locationId], $types ); |
| } |
| |
| // Check if we are at the root node, otherwise inherit style directives |
| $formats = array(); |
| if ( ( $element instanceof DOMElement ) && |
| !$element->parentNode instanceof DOMDocument ) |
| { |
| $formats = $this->inferenceFormattingRules( $element->parentNode ); |
| |
| // Some styles do not make sense to be inherited like background |
| // properties. |
| $formats = array_diff_key( $formats, array( |
| 'background-color' => true, |
| ) ); |
| } |
| |
| |
| // Apply all style directives, which match the location ID |
| foreach ( $this->styleDirectives as $directive ) |
| { |
| if ( ! $directive instanceof ezcDocumentPcssLayoutDirective ) |
| { |
| continue; |
| } |
| |
| if ( preg_match( $directive->getRegularExpression(), $locationId ) ) |
| { |
| $formats = $this->mergeFormattingRules( |
| $formats, |
| $directive->formats |
| ); |
| } |
| } |
| |
| $this->styleCache[$locationId] = $formats; |
| return $this->filterStyles( $formats, $types ); |
| } |
| |
| /** |
| * Get definition directives of given type |
| * |
| * Returns an array of definition directives, which matches the type passed |
| * as a parameter. |
| * |
| * @param string $type |
| * @return array |
| */ |
| public function getDefinitions( $type ) |
| { |
| $directives = array(); |
| foreach ( $this->styleDirectives as $directive ) |
| { |
| if ( ( $directive instanceof ezcDocumentPcssDeclarationDirective ) && |
| ( $directive->getType() === $type ) ) |
| { |
| $directives[] = $directive; |
| } |
| } |
| |
| return $directives; |
| } |
| } |
| ?> |