/**
  * 外部 CSS、埋め込み CSS を style 属性として XHTML テキストに割り当てます。
  * エンコーディング形式は変換されない点に注意して下さい。
  *
  * @param string $contents 適用対象の XHTML テキスト。
  * @return string style 属性を適用した XHTML テキストを返します。
  * @author Naomichi Yamakita <*****@*****.**>
  */
 public function assign($contents)
 {
     if (null_or_empty($contents)) {
         return $content;
     }
     if (!mb_check_encoding($contents, 'UTF-8')) {
         $contents = mb_convert_encoding($contents, 'UTF-8', 'Shift_JIS');
     }
     $this->response->setContentType('application/xhtml+xml; charset=Shift_JIS');
     // XML 宣言を取り除く (saveXML() コール時に付加されるため)
     if (preg_match('/^<\\?xml\\s[^>]+?\\?>\\s*/', $contents, $matches)) {
         $contents = substr($contents, strlen($matches[0]));
     }
     // xmlns 属性を取り除く (saveXML() コール時に付加されるため)
     $contents = preg_replace('/xmlns=["\'][^"\']+["\']/', '', $contents);
     // charset に UTF-8 以外のエンコーディングが指定されている場合、DOMDocument::loadHTML() が解析に失敗する
     $contents = preg_replace('/charset=Shift_JIS/i', 'charset=UTF-8', $contents);
     // 数値文字参照をエスケープ
     $contents = preg_replace('/&(#(?:\\d+|x[0-9a-fA-F]+)|[A-Za-z0-9]+);/', 'HTMLCSSINLINERESCAPE%$1%::::::::', $contents);
     try {
         $dom = new DOMDocument();
         $dom->loadXML($contents);
         $dom->formatOutput = TRUE;
         $dom->encoding = 'UTF-8';
         $dom->xmlStandalone = FALSE;
         $this->_xpath = new DOMXPath($dom);
         $this->loadCSS();
         // CSS をインライン化
         $css = $this->_htmlCSS->toArray();
         $styles = array();
         foreach ($css as $selector => $style) {
             // Selector2XPath は疑似要素の解析が不安定のためスルー
             if (strpos($selector, '@') !== FALSE) {
                 continue;
             }
             if (strpos($selector, ':') !== FALSE) {
                 $inline = NULL;
                 foreach ($style as $name => $value) {
                     $inline .= sprintf('%s:%s;', $name, $value);
                 }
                 $styles[] = sprintf('%s{%s}', $selector, $inline);
                 continue;
             }
             $xpath = HTML_CSS_Selector2XPath::toXPath($selector);
             try {
                 $elements = $this->_xpath->query($xpath);
                 if ($elements->length == 0) {
                     continue;
                 }
                 $inline = NULL;
                 foreach ($style as $name => $value) {
                     $inline .= sprintf('%s:%s;', $name, $value);
                 }
                 foreach ($elements as $element) {
                     if ($attributeStyle = $element->attributes->getNamedItem('style')) {
                         $attributeStyle->nodeValue = $inline . $attributeStyle->nodeValue;
                     } else {
                         $element->setAttribute('style', $inline);
                     }
                 }
                 // 無効なセレクタを無視
             } catch (Exception $e) {
             }
         }
         // 疑似クラスを <style> タグとして追加する
         if (sizeof($styles)) {
             $style = implode(PHP_EOL, $styles);
             $head = $this->_xpath->query('//head');
             $node = new DOMElement('style', $style);
             $head->item(0)->appendChild($node)->setAttribute('type', 'text/css');
         }
         $result = $dom->saveXML();
         $result = preg_replace('/encoding="UTF-8"/i', 'encoding="Shift_JIS"', $result);
         $result = preg_replace('/charset=UTF-8/i', 'charset=Shift_JIS', $result);
         $result = preg_replace('/HTMLCSSINLINERESCAPE%(#(?:\\d+|x[0-9a-fA-F]+)|[A-Za-z0-9]+)%::::::::/', '&$1;', $result);
         return $result;
     } catch (Exception $e) {
         // loadXML() がスローする例外が分かりにくいため、問題が起きたテンプレートのパスをメッセージに追加しておく
         $message = sprintf('Failed to parse template. (hint: %s) [%s]', $e->getMessage(), $this->renderer->getTemplatePath());
         throw new Mars_ParseException($message);
     }
 }
Beispiel #2
0
 /**
  * apply CSSをインライン化
  *
  * @param  string $document 変換を行うHTML文書
  * @param  string $base_dir CSSのベースディレクトリ(setBaseDirより優先)
  * @return string           変換されたHTML
  */
 public function apply($document, $base_dir = '')
 {
     /****************************************
      * 前処理
      ****************************************/
     if ($base_dir) {
         $this->base_dir = $base_dir;
     }
     // loadHTML/saveHTMLのバグに対応。XML宣言の一時退避
     $declaration = '';
     if (preg_match('/^<\\?xml\\s[^>]+?\\?>\\s*/', $document, $e)) {
         $declaration = $e[0];
         $document = substr($document, strlen($declaration));
     }
     // 文字参照をエスケープ
     $document = preg_replace('/&(#(?:\\d+|x[0-9a-fA-F]+)|[A-Za-z0-9]+);/', 'HTMLCSSINLINERESCAPE%$1%::::::::', $document);
     // 機種依存文字がエラーになる問題を回避するため、UTF-8に変換して処理
     $doc_encoding = mb_detect_encoding($document, 'sjis-win, UTF-8, eucjp-win');
     switch (strtolower($doc_encoding)) {
         case 'sjis-win':
             $html_encoding = 'Shift_JIS';
             break;
         case 'eucjp-win':
             $html_encoding = 'EUC-JP';
             break;
         default:
             $html_encoding = '';
             break;
     }
     if ($doc_encoding != 'UTF-8') {
         $document = str_replace(array('UTF-8', $html_encoding), array('@####UTF8####@', 'UTF-8'), $document);
         $document = mb_convert_encoding($document, 'UTF-8', $doc_encoding);
     }
     /****************************************
      * 本処理
      ****************************************/
     // XHTMLをパース
     $this->dom = new DOMDocument();
     $this->dom->loadHTML($document);
     $this->dom_xpath = new DOMXPath($this->dom);
     $this->loadCSS();
     // CSSをインライン化
     $css = $this->html_css->toArray();
     $add_style = array();
     foreach ($css as $selector => $style) {
         // 疑似要素は退避。@ルールはスルー(Selector2XPath的にバグでやすい)
         if (strpos($selector, '@') !== false) {
             continue;
         }
         if (strpos($selector, ':') !== false) {
             $add_style[] = $selector . '{' . $this->html_css->toInline($selector) . '}';
             continue;
         }
         $xpath = HTML_CSS_Selector2XPath::toXPath($selector);
         $elements = $this->dom_xpath->query($xpath);
         if ($elements->length == 0) {
             continue;
         }
         // inlineにするCSS文を構成(toInline($selector)だとh2, h3 などでうまくいかない問題があったため)
         $inline_style = '';
         foreach ($style as $k => $v) {
             $inline_style .= $k . ':' . $v . ';';
         }
         foreach ($elements as $element) {
             if ($attr_style = $element->attributes->getNamedItem('style')) {
                 // style要素が存在する場合は前方追記
                 #TODO: できれば、重複回避もしたい。少しロジックがまどろっこしい順序になってしまうのだが。。。
                 $attr_style->nodeValue = $inline_style . $attr_style->nodeValue;
             } else {
                 // style要素が存在しない場合は追加
                 $element->setAttribute('style', $inline_style);
             }
         }
     }
     // 疑似クラスを<style>タグとして追加
     if (!empty($add_style)) {
         $new_style = implode(PHP_EOL, $add_style);
         $new_style = str_replace(']]>', ']]]><![CDATA[]>', $new_style);
         $new_style = implode(PHP_EOL, array('<![CDATA[', $new_style, ']]>'));
         $head = $this->dom_xpath->query('//head');
         $new_style_node = new DOMElement('style', $new_style);
         $head->item(0)->appendChild($new_style_node)->setAttribute('type', 'text/css');
     }
     $result = $this->dom->saveHTML();
     /****************************************
      * 後処理
      ****************************************/
     // 文字コードを元に戻す
     if ($doc_encoding != 'UTF-8') {
         $result = mb_convert_encoding($result, $doc_encoding, 'UTF-8');
         $result = str_replace(array('UTF-8', '@####UTF8####@'), array($html_encoding, 'UTF-8'), $result);
     }
     // エスケープしていた参照を復元
     $result = preg_replace('/HTMLCSSINLINERESCAPE%(#(?:\\d+|x[0-9a-fA-F]+)|[A-Za-z0-9]+)%::::::::/', '&$1;', $result);
     // 退避したXML宣言を復元
     if (!empty($declaration)) {
         $result = $declaration . $result;
     }
     return $result;
 }