[ PHPXref.com ] [ Generated: Sun Jul 20 17:19:34 2008 ] [ dompdf 0.5 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/include/ -> stylesheet.cls.php (source)

   1  <?php
   2  /**
   3   * DOMPDF - PHP5 HTML to PDF renderer
   4   *
   5   * File: $RCSfile: stylesheet.cls.php,v $
   6   * Created on: 2004-06-01
   7   *
   8   * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
   9   *
  10   * This library is free software; you can redistribute it and/or
  11   * modify it under the terms of the GNU Lesser General Public
  12   * License as published by the Free Software Foundation; either
  13   * version 2.1 of the License, or (at your option) any later version.
  14   *
  15   * This library is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18   * Lesser General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU Lesser General Public License
  21   * along with this library in the file LICENSE.LGPL; if not, write to the
  22   * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  23   * 02111-1307 USA
  24   *
  25   * Alternatively, you may distribute this software under the terms of the
  26   * PHP License, version 3.0 or later.  A copy of this license should have
  27   * been distributed with this file in the file LICENSE.PHP .  If this is not
  28   * the case, you can obtain a copy at http://www.php.net/license/3_0.txt.
  29   *
  30   * The latest version of DOMPDF might be available at:
  31   * http://www.digitaljunkies.ca/dompdf
  32   *
  33   * @link http://www.digitaljunkies.ca/dompdf
  34   * @copyright 2004 Benj Carson
  35   * @author Benj Carson <benjcarson@digitaljunkies.ca>
  36   * @package dompdf
  37   * @version 0.3
  38   */
  39  
  40  /* $Id: stylesheet.cls.php,v 1.11 2006/04/06 21:29:41 benjcarson Exp $ */
  41  
  42  /**
  43   * The location of the default built-in CSS file.
  44   * {@link Stylesheet::DEFAULT_STYLESHEET}
  45   */
  46  define('__DEFAULT_STYLESHEET', DOMPDF_LIB_DIR . DIRECTORY_SEPARATOR . "res" . DIRECTORY_SEPARATOR . "html.css");
  47  
  48  /**
  49   * The master stylesheet class
  50   *
  51   * The Stylesheet class is responsible for parsing stylesheets and style
  52   * tags/attributes.  It also acts as a registry of the individual Style
  53   * objects generated by the current set of loaded CSS files and style
  54   * elements.
  55   *
  56   * @see Style
  57   * @package dompdf
  58   */
  59  class Stylesheet {
  60    
  61    
  62  
  63    /**
  64     * the location of the default built-in CSS file.
  65     * 
  66     */
  67    const DEFAULT_STYLESHEET = __DEFAULT_STYLESHEET; // Hack: can't
  68                                                     // concatenate stuff in
  69                                                     // const declarations,
  70                                                     // but I can do this?
  71    // protected members
  72  
  73    /**
  74     *  array of currently defined styles
  75     *  @var array
  76     */
  77    private $_styles;
  78  
  79    /**
  80     * base protocol of the document being parsed
  81     *
  82     * Used to handle relative urls.
  83     *
  84     * @var string 
  85     */
  86    private $_protocol;
  87  
  88    /**
  89     * base hostname of the document being parsed
  90     *
  91     * Used to handle relative urls.
  92     * @var string
  93     */
  94    private $_base_host;
  95  
  96    /**
  97     * base path of the document being parsed
  98     *
  99     * Used to handle relative urls.
 100     * @var string
 101     */
 102    private $_base_path;
 103  
 104    
 105    /**
 106     * the style defined by @page rules 
 107     *
 108     * @var Style
 109     */
 110    private $_page_style;
 111  
 112  
 113    /**
 114     * list of loaded files, used to prevent recursion
 115     *
 116     * @var array
 117     */
 118    private $_loaded_files;
 119    
 120    /**
 121     * accepted CSS media types 
 122     */
 123    static $ACCEPTED_MEDIA_TYPES = array("all", "static", "visual",
 124                                         "bitmap", "paged", "print");
 125    
 126    /**
 127     * The class constructor.
 128     *
 129     * The base protocol, host & path are initialized to those of
 130     * the current script.
 131     */
 132    function __construct() {
 133      $this->_styles = array();
 134      $this->_loaded_files = array();
 135      list($this->_protocol, $this->_base_host, $this->_base_path) = explode_url($_SERVER["SCRIPT_FILENAME"]);
 136      $this->_page_style = null;
 137    }
 138  
 139    /**
 140     * Set the base protocol
 141     *
 142     * @param string $proto
 143     */
 144    function set_protocol($proto) { $this->_protocol = $proto; }
 145  
 146    /**
 147     * Set the base host
 148     *
 149     * @param string $host
 150     */
 151    function set_host($host) { $this->_base_host = $host; }
 152  
 153    /**
 154     * Set the base path
 155     *
 156     * @param string $path
 157     */
 158    function set_base_path($path) { $this->_base_path = $path; }
 159  
 160  
 161    /**
 162     * add a new Style object to the stylesheet
 163     *
 164     * add_style() adds a new Style object to the current stylesheet, or
 165     * merges a new Style with an existing one.
 166     *
 167     * @param string $key   the Style's selector
 168     * @param Style $style  the Style to be added
 169     */
 170    function add_style($key, Style $style) {
 171      if (!is_string($key))
 172        throw new DOMPDF_Exception("CSS rule must be keyed by a string.");
 173  
 174      if ( isset($this->_styles[$key]) )
 175        $this->_styles[$key]->merge($style);
 176      else
 177        $this->_styles[$key] = clone $style;
 178    }
 179  
 180  
 181    /**
 182     * lookup a specifc Style object
 183     *
 184     * lookup() returns the Style specified by $key, or null if the Style is
 185     * not found.
 186     *
 187     * @param string $key   the selector of the requested Style
 188     * @return Style
 189     */
 190    function lookup($key) {
 191      if ( !isset($this->_styles[$key]) )
 192        return null;
 193      
 194      return $this->_styles[$key];
 195    }
 196  
 197    /**
 198     * create a new Style object associated with this stylesheet
 199     *
 200     * @return Style
 201     */
 202    function create_style() {
 203      return new Style($this);
 204    }
 205    
 206  
 207    /**
 208     * load and parse a CSS string
 209     *
 210     * @param string $css
 211     */
 212    function load_css(&$css) { $this->_parse_css($css); }
 213  
 214  
 215    /**
 216     * load and parse a CSS file
 217     *
 218     * @param string $file
 219     */
 220    function load_css_file($file) {
 221      global $_dompdf_warnings;
 222      
 223      // Prevent circular references
 224      if ( isset($this->_loaded_files[$file]) )
 225        return;
 226  
 227      $this->_loaded_files[$file] = true;
 228      
 229      list($this->_protocol, $this->_base_host, $this->_base_path, $filename) = explode_url($file);
 230      
 231      if ( !DOMPDF_ENABLE_REMOTE &&
 232           ($this->_protocol != "" && $this->_protocol != "file://") ) {
 233        record_warnings(E_USER_WARNING, "Remote CSS file '$file' requested, but DOMPDF_ENABLE_REMOTE is false.", __FILE__, __LINE__);
 234        return; 
 235      }
 236      
 237      // Fix submitted by Nick Oostveen for aliased directory support:
 238      if ( $this->_protocol == "" )
 239        $file = $this->_base_path . $filename;
 240      else
 241        $file = build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename);
 242      
 243      set_error_handler("record_warnings");
 244      $css = file_get_contents($file);
 245      restore_error_handler();
 246  
 247      if ( $css == "" ) {
 248        record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);;
 249        return;
 250      }
 251      
 252      $this->_parse_css($css);
 253  
 254    }
 255  
 256    /**
 257     * @link http://www.w3.org/TR/CSS21/cascade.html#specificity}
 258     *
 259     * @param string $selector
 260     * @return int
 261     */
 262    private function _specificity($selector) {
 263      // http://www.w3.org/TR/CSS21/cascade.html#specificity
 264  
 265      $a = ($selector === "!style attribute") ? 1 : 0;
 266      
 267      $b = min(mb_substr_count($selector, "#"), 255);
 268  
 269      $c = min(mb_substr_count($selector, ".") +
 270               mb_substr_count($selector, ">") +
 271               mb_substr_count($selector, "+"), 255);
 272      
 273      $d = min(mb_substr_count($selector, " "), 255);
 274  
 275      return ($a << 24) | ($b << 16) | ($c << 8) | ($d);
 276    }
 277  
 278  
 279    /**
 280     * converts a CSS selector to an XPath query.
 281     *
 282     * @param string $selector
 283     * @return string
 284     */
 285    private function _css_selector_to_xpath($selector) {
 286  
 287      // Collapse white space and strip whitespace around delimiters
 288  //     $search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
 289  //     $replace = array(" ", "\\1");
 290  //     $selector = preg_replace($search, $replace, trim($selector));
 291      
 292      // Initial query (non-absolute)
 293      $query = "//";
 294      
 295      // Parse the selector     
 296      //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
 297  
 298      $delimiters = array(" ", ">", ".", "#", "+", ":", "[");
 299  
 300      // Add an implicit space at the beginning of the selector if there is no
 301      // delimiter there already.
 302      if ( !in_array($selector{0}, $delimiters) )
 303        $selector = " $selector";
 304  
 305      $tok = "";
 306      $len = mb_strlen($selector);
 307      $i = 0;
 308                     
 309      while ( $i < $len ) {
 310  
 311        $s = $selector{$i};
 312        $i++;
 313  
 314        // Eat characters up to the next delimiter
 315        $tok = "";
 316  
 317        while ($i < $len) {
 318          if ( in_array($selector{$i}, $delimiters) )
 319            break;
 320          $tok .= $selector{$i++};
 321        }
 322  
 323        switch ($s) {
 324          
 325        case " ":
 326        case ">":
 327          // All elements matching the next token that are direct children of
 328          // the current token
 329          $expr = $s == " " ? "descendant" : "child";
 330  
 331          if ( mb_substr($query, -1, 1) != "/" )
 332            $query .= "/";
 333  
 334          if ( !$tok )
 335            $tok = "*";
 336          
 337          $query .= "$expr::$tok";
 338          $tok = "";
 339          break;
 340  
 341        case ".":
 342        case "#":
 343          // All elements matching the current token with a class/id equal to
 344          // the _next_ token.
 345  
 346          $attr = $s == "." ? "class" : "id";
 347  
 348          // empty class/id == *
 349          if ( mb_substr($query, -1, 1) == "/" )
 350            $query .= "*";
 351          
 352          $query .= "[@$attr=\"$tok\"]";
 353          $tok = "";
 354          break;
 355  
 356        case "+":
 357          // All sibling elements that folow the current token
 358          if ( mb_substr($query, -1, 1) != "/" )
 359            $query .= "/";
 360  
 361          $query .= "following-sibling::$tok";
 362          $tok = "";
 363          break;
 364  
 365        case ":":
 366          // Pseudo-classes
 367          switch ($tok) {
 368  
 369          case "first-child":
 370            break;
 371  
 372          case "link":
 373            $query .= "[@href]";
 374            $tok = "";
 375            break;
 376  
 377          case "first-line":
 378            break;
 379  
 380          case "first-letter":
 381            break;
 382  
 383          case "before":
 384            break;
 385  
 386          case "after":
 387            break;
 388          
 389          }
 390          
 391          break;
 392          
 393        case "[":
 394          // Attribute selectors.  All with an attribute matching the following token(s)
 395          $attr_delimiters = array("=", "]", "~", "|");
 396          $tok_len = mb_strlen($tok);
 397          $j = 0;
 398          
 399          $attr = "";
 400          $op = "";
 401          $value = "";
 402          
 403          while ( $j < $tok_len ) {
 404            if ( in_array($tok{$j}, $attr_delimiters) )
 405              break;
 406            $attr .= $tok{$j++};
 407          }
 408          
 409          switch ( $tok{$j} ) {
 410  
 411          case "~":
 412          case "|":
 413            $op .= $tok{$j++};
 414  
 415            if ( $tok{$j} != "=" )
 416              throw new DOMPDF_Exception("Invalid CSS selector syntax: invalid attribute selector: $selector");
 417  
 418            $op .= $tok{$j};
 419            break;
 420  
 421          case "=":
 422            $op = "=";
 423            break;
 424  
 425          }
 426         
 427          // Read the attribute value, if required
 428          if ( $op != "" ) {
 429            $j++;
 430            while ( $j < $tok_len ) {
 431              if ( $tok{$j} == "]" )
 432                break;
 433              $value .= $tok{$j++};
 434            }            
 435          }
 436         
 437          if ( $attr == "" )
 438            throw new DOMPDF_Exception("Invalid CSS selector syntax: missing attribute name");
 439  
 440          switch ( $op ) {
 441  
 442          case "":
 443            $query .=  "[@$attr]";
 444            break;
 445           
 446          case "=":
 447            $query .= "[@$attr$op\"$value\"]";
 448            break;
 449  
 450          case "~=":
 451            // FIXME: this will break if $value contains quoted strings
 452            // (e.g. [type~="a b c" "d e f"])
 453            $values = explode(" ", $value);
 454            $query .=  "[";
 455  
 456            foreach ( $values as $val ) 
 457              $query .= "@$attr=\"$val\" or ";
 458           
 459            $query = rtrim($query, " or ") . "]";
 460            break;
 461  
 462          case "|=":
 463            $values = explode("-", $value);
 464            $query .= "[";
 465  
 466            foreach ($values as $val)
 467              $query .= "starts-with(@$attr, \"$val\") or ";
 468  
 469            $query = rtrim($query, " or ") . "]";
 470            break;
 471           
 472          }
 473       
 474          break;
 475        }
 476      }
 477      $i++;
 478        
 479  //       case ":":
 480  //         // Pseudo selectors: ignore for now.  Partially handled directly
 481  //         // below.
 482  
 483  //         // Skip until the next special character, leaving the token as-is
 484  //         while ( $i < $len ) {
 485  //           if ( in_array($selector{$i}, $delimiters) )
 486  //             break;
 487  //           $i++;
 488  //         }
 489  //         break;
 490          
 491  //       default:
 492  //         // Add the character to the token
 493  //         $tok .= $selector{$i++};
 494  //         break;
 495  //       }
 496  
 497  //    }
 498      
 499      
 500      // Trim the trailing '/' from the query
 501      if ( mb_strlen($query) > 2 )
 502        $query = rtrim($query, "/");
 503      
 504      return $query;
 505    }
 506  
 507    /**
 508     * applies all current styles to a particular document tree
 509     *
 510     * apply_styles() applies all currently loaded styles to the provided
 511     * {@link Frame_Tree}.  Aside from parsing CSS, this is the main purpose
 512     * of this class.
 513     *
 514     * @param Frame_Tree $tree
 515     */
 516    function apply_styles(Frame_Tree $tree) {
 517  
 518      // Use XPath to select nodes.  This would be easier if we could attach
 519      // Frame objects directly to DOMNodes using the setUserData() method, but
 520      // we can't do that just yet.  Instead, we set a _node attribute_ in 
 521      // Frame->set_id() and use that as a handle on the Frame object via
 522      // Frame_Tree::$_registry.
 523  
 524      // We create a scratch array of styles indexed by frame id.  Once all
 525      // styles have been assigned, we order the cached styles by specificity
 526      // and create a final style object to assign to the frame.
 527  
 528      // FIXME: this is not particularly robust...    
 529  
 530      $styles = array();
 531      $xp = new DOMXPath($tree->get_dom());
 532  
 533      // Apply all styles in stylesheet
 534      foreach ($this->_styles as $selector => $style) {
 535  
 536        $query = $this->_css_selector_to_xpath($selector);
 537  //        pre_var_dump($selector);
 538  //        pre_var_dump($query);
 539  //        echo ($style);
 540        
 541        // Retrieve the nodes      
 542        $nodes = $xp->query($query);
 543  
 544        foreach ($nodes as $node) {
 545          //echo $node->nodeName . "\n";
 546          // Retrieve the node id
 547          if ( $node->nodeType != 1 ) // Only DOMElements get styles
 548            continue;
 549          
 550          $id = $node->getAttribute("frame_id");
 551  
 552          // Assign the current style to the scratch array
 553          $spec = $this->_specificity($selector);
 554          $styles[$id][$spec][] = $style;
 555        }
 556      }
 557  
 558      // Now create the styles and assign them to the appropriate frames.  (We
 559      // iterate over the tree using an implicit Frame_Tree iterator.)
 560      $root_flg = false;
 561      foreach ($tree->get_frames() as $frame) {
 562        // pre_r($frame->get_node()->nodeName . ":");
 563        
 564        // Find nearest DOMElement parent
 565        $p = $frame;      
 566        while ( $p = $p->get_parent() )
 567          if ($p->get_node()->nodeType == 1 )
 568            break;
 569        
 570        // Inherit a new style from the frame's nearest parent with a style
 571        if ( !$root_flg && $this->_page_style ) {
 572          $style = $this->_page_style;
 573          $root_flg = true;
 574  
 575        } else 
 576          $style = $this->create_style();
 577          
 578        if ( $p ) 
 579          $style->inherit( $p->get_style() );
 580  
 581        // Styles can only be applied directly to DOMElements; anonymous
 582        // frames inherit from their parent
 583        if ( $frame->get_node()->nodeType != 1 ) {
 584          $frame->set_style($style);
 585          continue;
 586        }
 587  
 588        $id = $frame->get_id();
 589  
 590        // Handle HTML 4.0 attributes
 591        Attribute_Translator::translate_attributes($frame);
 592      
 593        // Locate any additional style attributes      
 594        if ( ($str = $frame->get_node()->getAttribute("style")) !== "" ) {
 595          $spec = $this->_specificity("!style attribute");
 596          $styles[$id][$spec][] = $this->_parse_properties($str);
 597        }
 598        
 599        // Grab the applicable styles
 600        if ( isset($styles[$id]) ) {
 601          
 602          $applied_styles = $styles[ $frame->get_id() ];
 603  
 604          // Sort by specificity
 605          ksort($applied_styles);
 606  
 607          // Merge the new styles with the inherited styles
 608          foreach ($applied_styles as $arr) {
 609            foreach ($arr as $s) 
 610              $style->merge($s);
 611          }
 612        }
 613  
 614  //       pre_r($frame->get_node()->nodeName . ":");
 615  //      echo "<pre>";
 616  //      echo $style;
 617  //      echo "</pre>";
 618        $frame->set_style($style);
 619        
 620      }
 621      
 622      // We're done!  Clean out the registry of all styles since we
 623      // won't be needing this later.
 624      foreach ( array_keys($this->_styles) as $key ) {
 625        unset($this->_styles[$key]);
 626      }
 627      
 628    }
 629    
 630  
 631    /**
 632     * parse a CSS string using a regex parser
 633     *
 634     * Called by {@link Stylesheet::parse_css()} 
 635     *
 636     * @param string $str 
 637     */
 638    private function _parse_css($str) {
 639  
 640      // Destroy comments
 641      $css = preg_replace("'/\*.*?\*/'si", "", $str);
 642  
 643      // FIXME: handle '{' within strings, e.g. [attr="string {}"]
 644  
 645      // Something more legible:
 646      $re =
 647        "/\s*                                   # Skip leading whitespace                             \n".
 648        "( @([^\s]+)\s+([^{;]*) (?:;|({)) )?    # Match @rules followed by ';' or '{'                 \n".
 649        "(?(1)                                  # Only parse sub-sections if we're in an @rule...     \n".
 650        "  (?(4)                                # ...and if there was a leading '{'                   \n".
 651        "    \s*( (?:(?>[^{}]+) ({)?            # Parse rulesets and individual @page rules           \n".
 652        "            (?(6) (?>[^}]*) }) \s*)+?  \n".
 653        "       )                               \n".
 654        "   })                                  # Balancing '}'                                \n".
 655        "|                                      # Branch to match regular rules (not preceeded by '@')\n".
 656        "([^{]*{[^}]*}))                        # Parse normal rulesets\n".
 657        "/xs";
 658       
 659      if ( preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false )
 660        // An error occured
 661        throw new DOMPDF_Exception("Error parsing css file: preg_match_all() failed.");
 662  
 663      // After matching, the array indicies are set as follows:
 664      //
 665      // [0] => complete text of match
 666      // [1] => contains '@import ...;' or '@media {' if applicable
 667      // [2] => text following @ for cases where [1] is set
 668      // [3] => media types or full text following '@import ...;'
 669      // [4] => '{', if present
 670      // [5] => rulesets within media rules
 671      // [6] => '{', within media rules
 672      // [7] => individual rules, outside of media rules
 673      //
 674      //pre_r($matches);
 675      foreach ( $matches as $match ) {
 676        $match[2] = trim($match[2]);
 677  
 678        if ( $match[2] !== "" ) {
 679          // Handle @rules
 680          switch ($match[2]) {
 681  
 682          case "import":          
 683            $this->_parse_import($match[3]);
 684            break;
 685  
 686          case "media":
 687            if ( in_array(mb_strtolower(trim($match[3])), self::$ACCEPTED_MEDIA_TYPES ) ) {
 688              $this->_parse_sections($match[5]);
 689            }
 690            break;
 691  
 692          case "page":
 693            // Store the style for later...
 694            if ( is_null($this->_page_style) )
 695              $this->_page_style = $this->_parse_properties($match[5]);
 696            else
 697              $this->_page_style->merge($this->_parse_properties($match[5]));
 698            break;
 699            
 700          default:
 701            // ignore everything else
 702            break;
 703          }
 704  
 705          continue;
 706        }
 707  
 708        if ( $match[7] !== "" ) 
 709          $this->_parse_sections($match[7]);
 710        
 711      }
 712    }
 713  
 714    
 715    /**
 716     * parse @import{} sections
 717     *
 718     * @param string $url  the url of the imported CSS file
 719     */
 720    private function _parse_import($url) {
 721      $arr = preg_split("/[\s\n]/", $url);
 722      $url = array_pop($arr);
 723      $accept = false;
 724      
 725      if ( count($arr) > 0 ) {
 726        
 727        // @import url media_type [media_type...]
 728        foreach ( $arr as $type ) {
 729          if ( in_array($type, self::$ACCEPTED_MEDIA_TYPES) ) {
 730            $accept = true;
 731            break;
 732          }
 733        }
 734        
 735      } else
 736        // unconditional import
 737        $accept = true;
 738      
 739      if ( $accept ) {
 740        $url = str_replace(array('"',"url", "(", ")"), "", $url);
 741        // Store our current base url properties in case the new url is elsewhere
 742        $protocol = $this->_protocol;
 743        $host = $this->_base_host;
 744        $path = $this->_base_path;
 745  
 746        // If the protocol is php, assume that we will import using file://
 747        $url = build_url($protocol == "php://" ? "file://" : $protocol, $host, $path, $url);
 748        
 749        $this->load_css_file($url);
 750        
 751        // Restore the current base url
 752        $this->_protocol = $protocol;
 753        $this->_base_host = $host;
 754        $this->_base_path = $path;
 755      }
 756      
 757    }
 758  
 759    /**
 760     * parse regular CSS blocks
 761     *
 762     * _parse_properties() creates a new Style object based on the provided
 763     * CSS rules.
 764     *
 765     * @param string $str  CSS rules
 766     * @return Style
 767     */
 768    private function _parse_properties($str) {
 769      $properties = explode(";", $str);
 770  
 771      // Create the style
 772      $style = new Style($this);
 773      foreach ($properties as $prop) {
 774  
 775        $prop = trim($prop);
 776  
 777        if ($prop == "")
 778          continue;
 779  
 780        $i = mb_strpos($prop, ":");
 781        if ( $i === false )
 782          continue;
 783  
 784        $prop_name = mb_strtolower(mb_substr($prop, 0, $i));
 785        $value = mb_substr($prop, $i+1);
 786        $style->$prop_name = $value;
 787  
 788      }
 789  
 790      return $style;
 791    }
 792  
 793    /**
 794     * parse selector + rulesets
 795     *
 796     * @param string $str  CSS selectors and rulesets
 797     */
 798    private function _parse_sections($str) {
 799      // Pre-process: collapse all whitespace and strip whitespace around '>',
 800      // '.', ':', '+', '#'
 801      
 802      $patterns = array("/[\\s\n]+/", "/\\s+([>.:+#])\\s+/");
 803      $replacements = array(" ", "\\1");
 804      $str = preg_replace($patterns, $replacements, $str);
 805  
 806      $sections = explode("}", $str);
 807      foreach ($sections as $sect) {
 808        $i = mb_strpos($sect, "{");
 809  
 810        $selectors = explode(",", mb_substr($sect, 0, $i));
 811        $style = $this->_parse_properties(trim(mb_substr($sect, $i+1)));
 812  
 813        // Assign it to the selected elements
 814        foreach ($selectors as $selector) {
 815          $selector = trim($selector);
 816          
 817          if ($selector == "")
 818            continue;
 819          
 820          $this->add_style($selector, $style);
 821        }
 822      }
 823    }  
 824  
 825    /**
 826     * dumps the entire stylesheet as a string
 827     *
 828     * Generates a string of each selector and associated style in the
 829     * Stylesheet.  Useful for debugging.
 830     *
 831     * @return string
 832     */
 833    function __toString() {
 834      $str = "";
 835      foreach ($this->_styles as $selector => $style) 
 836        $str .= "$selector => " . $style->__toString() . "\n";
 837  
 838      return $str;
 839    }
 840  }
 841  ?>


[ Powered by PHPXref - Served by Debian GNU/Linux ]