Version: current

     1 <?php
     2
     3 /**
     4  * CSSContentParser enables parsing & assertion running of HTML content via CSS selectors.
     5  *
     6  * It works by converting the content to XHTML using tidy, rewriting the CSS selectors as XPath queries, and executing
     7  * those using SimpeXML.
     8  *
     9  * It was built to facilitate testing using PHPUnit and contains a number of assert methods that will throw PHPUnit
    10  * assertion exception when applicable.
    11  *
    12  * Tries to use the PHP Tidy extension (http://php.net/tidy),
    13  * and falls back to the "tidy" CLI tool. If none of those exists,
    14  * the string is parsed directly without sanitization.
    15  *
    16  * @package sapphire
    17  * @subpackage core
    18  */
    19 class CSSContentParser extends Object {
    20     protected $simpleXML = null;
    21
    22     function __construct($content) {
    23         if(extension_loaded('tidy')) {
    24             // using the tiny php extension
    25             $tidy = new Tidy();
    26             $tidy->parseString(
    27                 $content,
    28                 array(
    29                     'output-xhtml' => true,
    30                     'numeric-entities' => true,
    31                 ),
    32                 'utf8'
    33             );
    34             $tidy->cleanRepair();
    35             $tidy = str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$tidy);
    36             $tidy = str_replace('&#160;','',$tidy);
    37
    38         } elseif(@shell_exec('which tidy')) {
    39             // using tiny through cli
    40             $CLI_content = escapeshellarg($content);
    41             $tidy = `echo $CLI_content | tidy -n -q -utf8 -asxhtml 2> /dev/null`;
    42             $tidy = str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$tidy);
    43             $tidy = str_replace('&#160;','',$tidy);
    44         } else {
    45             // no tidy library found, hence no sanitizing
    46             $tidy = $content;
    47         }
    48
    49         $this->simpleXML = simplexml_load_string($tidy, 'SimpleXMLElement', LIBXML_NOWARNING);
    50
    51         parent::__construct();
    52     }
    53
    54     /**
    55      * Returns a number of SimpleXML elements that match the given CSS selector.
    56      * Currently the selector engine only supports querying by tag, id, and classs
    57      */
    58     function getBySelector($selector) {
    59         $xpath = $this->selector2xpath($selector);
    60         return $this->simpleXML->xpath($xpath);
    61     }
    62
    63     /**
    64      * Converts a CSS selector into an equivalent xpath expression.
    65      * Currently the selector engine only supports querying by tag, id, and classs
    66      */
    67     function selector2xpath($selector) {
    68         $parts = preg_split('/\\s+/', $selector);
    69         $xpath = "";
    70         foreach($parts as $part) {
    71             if(preg_match('/^([A-Za-z][A-Za-z0-9]*)/', $part, $matches)) {
    72                 $xpath .= "//$matches[1]";
    73             } else {
    74                 $xpath .= "//*";
    75             }
    76             $xfilters = array();
    77             if(preg_match('/#([^#.\[]+)/', $part, $matches)) {
    78                 $xfilters[] = "@id='$matches[1]'";
    79             }
    80             if(preg_match('/\.([^#.\[]+)/', $part, $matches)) {
    81                 $xfilters[] = "contains(@class,'$matches[1]')";
    82             }
    83             if($xfilters) $xpath .= '[' . implode(',', $xfilters) . ']';
    84         }
    85         return $xpath;
    86     }
    87
    88 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -