You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

235 lines
6.3 KiB

<?php
/*
* InlineStyle MIT License
*
* Copyright (c) 2010 Christiaan Baartse
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Parses a html file and applies all embedded and external stylesheets inline
*
* @author Christiaan Baartse <christiaan@baartse.nl>
* @copyright 2010 Christiaan Baartse
*/
class InlineStyle
{
/**
* @var DOMDocument the HTML as DOMDocument
*/
protected $_dom;
/**
* @var CSSQuery instance to use css based selectors on our DOMDocument
*/
protected $_cssquery;
/**
* Prepare all the necessary objects
*
* @param string $html
*/
public function __construct($html) {
if(!class_exists("CSSQuery")) {
throw new Exception(
"InlineStyle needs the CSSQuery class");
}
$html = (string) $html;
$this->_dom = new DOMDocument();
if(file_exists($html)) {
$this->_dom->loadHTMLFile($html);
}
else {
$this->_dom->loadHTML($html);
}
$this->_cssquery = new CSSQuery($this->_dom);
}
/**
* Applies one or more stylesheets to the current document
*
* @param string $stylesheet
* @return InlineStyle self
*/
public function applyStylesheet($stylesheet) {
$stylesheet = (array) $stylesheet;
foreach($stylesheet as $ss) {
foreach($this->parseStylesheet($ss) as $arr) {
list($selector, $style) = $arr;
try {
$this->applyRule($selector, $style);
}
catch(Exception $e) { // ne pas casser sur un sélecteur inconnu ou une erreur dans le CSS
spip_log("Erreur dans la transcription des styles en ligne: ".$e->getMessage(),'facteur');
}
}
}
return $this;
}
/**
* Applies a style rule on the document
* @param string $selector
* @param string $style
* @return InlineStyle self
*/
public function applyRule($selector, $style) {
$selector = trim(trim($selector), ",");
if($selector) {
$nodes = array();
foreach(explode(",", $selector) as $sel) {
if(false === stripos($sel, ":hover") &&
false === stripos($sel, ":active") &&
false === stripos($sel, ":visited")) {
$nodes = array_merge($nodes, $this->_cssquery->query($sel));
}
}
$style = $this->_styleToArray($style);
foreach($nodes as $node) {
$current = $node->hasAttribute("style") ?
$this->_styleToArray($node->getAttribute("style")) :
array();
$current = $this->_mergeStyles($current, $style);
$st = array();
foreach($current as $prop => $val) {
$st[] = "{$prop}:{$val}";
}
$node->setAttribute("style", implode(";", $st));
}
}
return $this;
}
/**
* Returns the DOMDocument as html
*
* @return string the HTML
*/
public function getHTML()
{
return $this->_dom->saveHTML();
}
/**
* Recursively extracts the stylesheet nodes from the DOMNode
* @param DOMNode $node leave empty to extract from the whole document
* @return array the extracted stylesheets
*/
public function extractStylesheets(DOMNode $node = null, $base = "")
{
if(null === $node) {
$node = $this->_dom;
}
$stylesheets = array();
if(strtolower($node->nodeName) === "style") {
$stylesheets[] = $node->nodeValue;
$node->parentNode->removeChild($node);
}
else if(strtolower($node->nodeName) === "link") {
if($node->hasAttribute("href")) {
$href = $node->getAttribute("href");
if($base && false === strpos($href, "://")) {
$href = "{$base}/{$href}";
}
$ext = @file_get_contents($href);
if($ext) {
$stylesheets[] = $ext;
$node->parentNode->removeChild($node);
}
}
}
if($node->hasChildNodes()) {
foreach($node->childNodes as $child) {
$stylesheets = array_merge($stylesheets,
$this->extractStylesheets($child, $base));
}
}
return $stylesheets;
}
/**
* Parses a stylesheet to selectors and properties
* @param string $stylesheet
* @return array
*/
public function parseStylesheet($stylesheet) {
$parsed = array();
$stylesheet = $this->_stripStylesheet($stylesheet);
$stylesheet = trim(trim($stylesheet), "}");
foreach(explode("}", $stylesheet) as $rule) {
list($selector, $style) = explode("{", $rule, 2);
$parsed[] = array(trim($selector), trim(trim($style), ";"));
}
return $parsed;
}
/**
* Parses style properties to a array which can be merged by mergeStyles()
* @param string $style
* @return array
*/
protected function _styleToArray($style) {
$styles = array();
$style = trim(trim($style), ";");
if($style) {
foreach(explode(";",$style) as $props) {
$props = trim(trim($props), ";");
list($prop, $val) = explode(":", $props);
$styles[$prop] = $val;
}
}
return $styles;
}
/**
* Merges two sets of style properties taking !important into account
* @param array $styleA
* @param array $styleB
* @return array
*/
protected function _mergeStyles(array $styleA, array $styleB) {
foreach($styleB as $prop => $val) {
if(!isset($styleA[$prop]) ||
substr(str_replace(" ", "", strtolower($styleA[$prop])), -10) !==
"!important") {
$styleA[$prop] = $val;
}
}
return $styleA;
}
protected function _stripStylesheet($s)
{
$s = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!','', $s);
$s = str_replace(array("\r\n","\r","\n","\t",' ',' ',' '),'',$s);
$s = str_replace('{ ', '{', $s);
$s = str_replace(' }', '}', $s);
$s = str_replace('; ', ';', $s);
return $s;
}
}