/**
  * Converts the given XML tree and the given XSL file to a (HTML) file, with respect to the given XSL parameters.
  * The given XMLTree parameter may be of the SimpleXMLElement or the DOMDocument type, however, the latter is the fastest.
  * The generated output may be considered to be formatted properly.
  * @param mixed The XML Tree as a SimpleXMLElement or a DOMDocument
  * @param mixed The fullpath to the XSL file, or an instanced \SimpleXMLElement XSL tree
  * @param \System\Collection\Map The map with the parameters, or null for no parameters or default
  */
 public final function render()
 {
     $args = func_get_args();
     if (count($args) != 3) {
         throw new \InvalidArgumentException('Invalid amount of arguments given.');
     }
     list($xmlTree, $xslFile, $parameters) = $args;
     $processor = new \XSLTProcessor();
     //register the php functions
     if (count(self::$registeredPHPFunctions) > 0) {
         $processor->registerPHPFunctions(self::$registeredPHPFunctions);
     }
     //load the xsl file intro a dom
     $xsl = new \DOMDocument();
     if ($xslFile instanceof \SimpleXMLElement) {
         $xsl->loadXML($xslFile->asXML());
     } else {
         if (!file_exists($xslFile)) {
             throw new \System\Error\Exception\FileNotFoundException('XSL File: ' . $xslFile . ' cannot be found');
         }
         $xsl->load($xslFile);
     }
     //attach the xsl dom to the processor
     $processor->importStylesheet($xsl);
     //when we run as a local debug system, we output the profiling information
     if (defined('DEBUG')) {
         $processor->setProfiling(PATH_LOGS . 'XSLTRenderProfiling.txt');
     }
     $dom = new \DOMDocument();
     switch (true) {
         case $xmlTree instanceof \SimpleXMLElement:
             //we need to convert to a domdocument
             $dom = \System\XML\XML::convertSimpleXMLElementToDOMDocument($xmlTree);
             break;
         case $xmlTree instanceof \DOMDocument:
             //no conversion needed
             break;
         default:
             throw new \InvalidArgumentException('Given XML tree is of non supported tree type: ' . get_class($xmlTree));
     }
     $this->preprocessParameters($parameters);
     //we do not need any namespaces
     $processor->setParameter('', $parameters->getArrayCopy());
     $output = $processor->transformToXML($dom);
     if (!$output) {
         throw new \Exception('Could not transform the given XML and XSL to a valid HTML page');
     }
     if (MINIFY_ENABLE) {
         $output = \System\Web\Minify\HTML\Minify::minify($output);
     }
     $this->addToBuffer($output);
 }
 /**
  * The main function of this object which simply renders the accumulated data using PHP's XSLTProcessor
  * @access public
  */
 public function render()
 {
     try {
         $xsltproc = new \XSLTProcessor();
         $xsltproc->importStylesheet($this->_styleSheet);
         if (isset($this->_profilerLogPath)) {
             $xsltproc->setProfiling($this->_profilerLogPath);
         }
         return $xsltproc->transformToXML($this->_inputData);
     } catch (Exception $e) {
         echo "Could not process the Datafile with the given Stylesheet: " . $e->getMessage();
     }
 }
<?php

$file = '/etc/passwd' . chr(0) . 'asdf';
$doc = new DOMDocument();
$proc = new XSLTProcessor();
var_dump($proc->setProfiling($file));
var_dump($proc->transformToURI($doc, $file));
require 'library/class.cli-configuration.php';
require 'library/functions.sanity-checks.php';
$Configuration = new CLIConfiguration(array('help::' => null, 'u=url:' => '', 'v=verbose' => 0, 'version' => null, 't=xsl:' => '', 'x=param:' => array(), 'p=preset:' => null));
$Configuration->SetFlagTriggers(array('help' => 'CLIVersion', 'verbose' => 'CLIVerbosity', 'param' => 'XSLParam'));
$Configuration->AddPresetFlagTrigger();
$Collected = $Configuration->CollectArguments();
$Parameters = $Configuration->Param;
$Url = $Configuration->Url;
$Xsl = $Configuration->Xsl;
// Sanity Checks
SanityCheck(!empty($Url), 'No URL specified', 'CLIVersion');
SanityCheck(!empty($Xsl), 'No Transformation XSL file specified');
SanityCheck(file_exists($Xsl), '"' . $Xsl . '" does not exist');
libxml_use_internal_errors(true);
$XsltProcessor = new XSLTProcessor();
$XsltProcessor->setProfiling('_output/profiling.txt');
$XmlString = '';
$XmlData = new DOMDocument('1.0', 'utf-8');
$XslData = new DOMDocument('1.0', 'utf-8');
$DomErrors = new DOMDocument('1.0', 'utf-8');
$XsltProcessor->setParameter('', 'request-url', $Url);
if (is_array($Parameters)) {
    foreach ($Parameters as $Key => $Value) {
        $XsltProcessor->setParameter('', $Key, $Value);
    }
}
// Grab the remote XML file
$C = curl_init($Url);
curl_setopt_array($C, array(CURLOPT_USERAGENT => 'XSL-Transformation/' . VERSION . ' (like Feedburner)', CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => true));
$XmlString = curl_exec($C);
curl_close($C);
Exemple #5
0
 /**
  * проверяет возможности клиента и выдает как есть или выполняет трансформации сам
  * и выдает готовый html
  * @param string $data выходные данные в xml
  * @param boolean $html явное указание формата html
  * @param string $documentURI так как xml-документ $data передан строкой иногда требуется указать путь до него чтобы нашлись схемы
  * @param boolean $forceValidation NULL - валидация если в домашнем каталоге, TRUE: форсировать проверку по схеме/tidy всегда, FALSE - не проверять по схеме/tidy
  * @param string $htmlContentType миме-тип для вывода html, по-умолчанию text/html, для вывода html+xul передать application/xml
  */
 public static function tryHTML($data, $html = FALSE, $documentURI = NULL, $forceValidation = NULL, $htmlContentType = "text/html")
 {
     $outputDom = NULL;
     $xsltResult = NULL;
     $debug = FALSE;
     $xsltProfiler = NULL;
     if (self::$done == TRUE) {
         trigger_error("Output::tryHTML() called twice?");
     }
     $xsltStart = $xsltStop = 0;
     //минипрофайлер
     if ($html == FALSE && isset($_SERVER["HTTP_ACCEPT"])) {
         //тут пока неразбериха с text/xml по старому и application/xml по новому
         //опера по-новому, ие по-старому, мозиллы и так и сяк
         if (strpos($_SERVER["HTTP_ACCEPT"], "/xml") !== FALSE) {
             //тем кто явно грит "понимаю xml" сбросим флаг html
             $html = FALSE;
             if ("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" == $_SERVER["HTTP_ACCEPT"]) {
                 //но некоторые говорят что понимают, но на самом деле не понимают, это андроиды 2.х их мы отлавливаем по кривому заголовку
                 //под раздачу также попадают ближайщие родственники, которые реально умеют, но их не отличить от больных - сафари 4 (айфон 4).
                 //остальные родственники успешно вылечились - сафари 5, хромы и хромиумы и другие вэбкитообразные.
                 //используем заголовок Accept а не Usera-gent потому что
                 //1. уже есть Vary: Accept - чтоб не плодить ветвление в прокси и кешах
                 //2. у больных особей есть переключатель "mobile view" который влияет на User-agent-а
                 // @seealso http://www.gethifi.com/blog/browser-rest-http-accept-headers
                 // @seealso https://developer.mozilla.org/en-US/docs/HTTP/Content_negotiation
                 $html = TRUE;
             }
         } elseif (strpos($_SERVER["HTTP_ACCEPT"], "text/html") !== FALSE) {
             //тем кто не понимает xml но явно грит что умеет html
             $html = TRUE;
         }
     } elseif (isset($_SERVER["HTTP_ACCEPT"])) {
         //"обратная автоматика" - даже если форсирован HTML но клиент его "не хочет" или "не может", но обещает что поймет XML - спрыгиваем на XML
         if (strpos($_SERVER["HTTP_ACCEPT"], "text/html") === FALSE && strpos($_SERVER["HTTP_ACCEPT"], "/xml") !== FALSE) {
             //тем кто явно заявляет что понимает xml и не умеет html
             $html = FALSE;
         }
     }
     //$html=TRUE;
     //подготовить стили для трансформации на php
     if ($debug || $html) {
         $outputDom = new \DOMDocument();
         $outputDom->loadXML($data);
         if ($documentURI) {
             $outputDom->documentURI = $documentURI;
         }
         //валидация данных xml-схемой
         if ($debug) {
             $matches = NULL;
             //добываем имя схемы и проверяем по ней (или xmlreader?)
             if (preg_match("/schemaLocation=\".+\\s([a-zA-Z0-9_\\/\\.\\-]+)\"/", $data, $matches)) {
                 $outputDom->schemaValidate(($documentURI ? dirname($documentURI) . "/" : "") . $matches[1]);
                 //                } else {
                 //                    throw new \Exception("cant find schemaLocation");
             }
         }
         $matches = NULL;
         //добываем имя стиля из хмл-а (или xmlreader?)
         if ($outputDom->firstChild->nodeType == XML_PI_NODE && $outputDom->firstChild->target == "xml-stylesheet") {
             if (preg_match("/href\\s*=\\s*\"(.+)\"/", $outputDom->firstChild->data, $matches)) {
                 $oldHeaders = headers_list();
                 //время трансформации считаем общее - вместе с загрузкой документов
                 $xsltStart = microtime(TRUE);
                 $xsl = new \DomDocument();
                 // надо взять таблицу стилей локально
                 $xsl_file = dirname($_SERVER["SCRIPT_FILENAME"]) . "/" . str_replace("http://" . $_SERVER["HTTP_HOST"] . dirname($_SERVER["PHP_SELF"]), "", $matches[1]);
                 //error_log(dirname($_SERVER["SCRIPT_FILENAME"]));
                 //error_log($_SERVER["PHP_SELF"]);
                 //$xsl->load($matches[1]);
                 $xsl->load("{$xsl_file}");
                 $proc = new \XSLTProcessor();
                 if ($xsltProfiler) {
                     $proc->setProfiling($xsltProfiler);
                 }
                 $proc->importStyleSheet($xsl);
                 //регистрируем на себя обращения к файлам
                 stream_wrapper_unregister("file") or die(__FILE__ . __LINE__);
                 stream_wrapper_register("file", static::CLASSNAME) or die(__FILE__ . __LINE__);
                 //вешаем на обработчик выхода ловушку - если вложенный скрипт попытается сделать exit или die
                 register_shutdown_function(array(static::CLASSNAME, "checkDone"));
                 //на время трансформации ставим свой специальный обработчик ошибок
                 set_error_handler(array(static::CLASSNAME, "xsltErrorHandler"));
                 $xsltResult = $proc->transformToXML($outputDom);
                 restore_error_handler();
                 if (self::$xsltErrors != NULL) {
                     //а сообщаем об ошибках как обычно
                     trigger_error("XSLTProcessor::transformToXml(): " . self::$xsltErrors);
                 }
                 //ставим маркер что управление нам вернули
                 self::$done = TRUE;
                 unset($proc, $xsl);
                 //восстанавливаем дефолтный streamwrapper для file://
                 stream_wrapper_restore("file") or die(__FILE__ . __LINE__);
                 //закончили трансформацию
                 $xsltStop = microtime(TRUE);
                 if ($xsltProfiler) {
                     //ничего секретного там нет - даем всем почитать
                     chmod($xsltProfiler, 0644);
                 }
                 //сравним хедеры до и после
                 $diffHeaders = array_diff(headers_list(), $oldHeaders);
                 //сбрасываем все хедеры которых "тут не стояло"
                 foreach ($diffHeaders as $h) {
                     $matches = explode(":", $h);
                     header_remove($matches[0]);
                 }
                 //сбросим текущие чтобы добавить старые
                 foreach ($oldHeaders as $h) {
                     $matches = explode(":", $h);
                     header_remove($matches[0]);
                 }
                 //востановим старые как были
                 foreach ($oldHeaders as $h) {
                     header($h, FALSE);
                 }
                 unset($diffHeaders, $oldHeaders, $h);
             }
         } else {
             //стиль не найден - html не получится - сбрасываем флаг
             $html = FALSE;
         }
     }
     self::$done = TRUE;
     //валидация выходного html с помощью tidy и по dtd-схеме
     if (1 != 1 && $debug && $xsltResult) {
         //http://dab.net.ilb.ru/doc/htmltidy-5.10.26-r2/html/quickref.html
         if (strncmp($xsltResult, "<!DOCTYPE html SYSTEM \"about:legacy-compat\">", 44)) {
             $config = array("output-xhtml" => TRUE, "doctype" => "strict");
             $tidy = tidy_parse_string($xsltResult, $config, "UTF8");
             //для tidy БЕЗ минуса
             $tidy->diagnose();
             if (tidy_error_count($tidy) + tidy_warning_count($tidy)) {
                 // tidy возвращает строку с ошибкой, пронумеровал для облегчения отладки
                 $xsltResultLines = explode(PHP_EOL, $xsltResult);
                 $xsltResultWithLines = "";
                 foreach ($xsltResultLines as $lineNumber => $line) {
                     $xsltResultWithLines .= sprintf("%04d: ", $lineNumber + 1) . $line . PHP_EOL;
                 }
                 throw new Exception("tidy validation errors: " . $tidy->errorBuffer . PHP_EOL . $xsltResultWithLines);
             }
             //уберемся за собой
             unset($tidy, $config);
         } else {
             //html5 проверяем через локально развернутый сервис validator.nu
             $ch = curl_init();
             curl_setopt($ch, CURLOPT_TIMEOUT, 30);
             curl_setopt($ch, CURLOPT_VERBOSE, 0);
             curl_setopt($ch, CURLOPT_HEADER, 0);
             //TODO разобраться с нумерацией строк (с формы строки соответсвуют исходнику, а когда через сервис все в одну строку)
             curl_setopt($ch, CURLOPT_URL, "http://devel.net.ilb.ru:8888/?parser=html5&out=gnu");
             curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/html"));
             curl_setopt($ch, CURLOPT_POST, 1);
             curl_setopt($ch, CURLOPT_POSTFIELDS, $xsltResult);
             ob_start();
             curl_exec($ch);
             $out = ob_get_contents();
             ob_end_clean();
             $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
             if ($code != 200) {
                 trigger_error("curl failed " . curl_error($ch) . " HTTP" . $code . PHP_EOL . $out);
             }
             curl_close($ch);
             //исправление косяков html5-валидатора связанных с отсутствием стандарта
             //пропускаем предупреждения связанные с input type="date"
             $out = trim(preg_replace("/^:.+info warning: The “date” input type is so far supported properly.*/m", "", $out));
             if ($out) {
                 $xsltResultLines = explode(PHP_EOL, $xsltResult);
                 $xsltResultWithLines = "";
                 foreach ($xsltResultLines as $lineNumber => $line) {
                     $xsltResultWithLines .= sprintf("%04d: ", $lineNumber + 1) . $line . PHP_EOL;
                 }
                 throw new \Exception("validator.nu errors: " . PHP_EOL . $out . PHP_EOL . $xsltResultWithLines);
             }
             //уберемся за собой
             unset($ch, $out, $code);
         }
         $xsltResultDoc = new \DOMDocument();
         //поиск схемы для проверки xhtml - ищем по обычным путям как и классы
         $schemasPath = NULL;
         foreach (explode(PATH_SEPARATOR, get_include_path()) as $p) {
             $p = $p . DIRECTORY_SEPARATOR . "schemas";
             if (file_exists($p . DIRECTORY_SEPARATOR . "xhtml" . DIRECTORY_SEPARATOR . "dtd" . DIRECTORY_SEPARATOR . "xhtml1-strict.dtd")) {
                 $schemasPath = $p;
                 break;
             }
         }
         $xsltResultXml = str_replace(array("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"), array($schemasPath . DIRECTORY_SEPARATOR . "xhtml" . DIRECTORY_SEPARATOR . "dtd" . DIRECTORY_SEPARATOR . "xhtml1-strict.dtd", $schemasPath . DIRECTORY_SEPARATOR . "xhtml11" . DIRECTORY_SEPARATOR . "dtd" . DIRECTORY_SEPARATOR . "xhtml11-flat.dtd"), $xsltResult);
         //поддерживается только Strict. как явно указать валидатору xml-catalog непонятно (с комстроки работает xmllint --catalog)
         $xsltResultDoc->loadXML($xsltResultXml);
         if ($documentURI) {
             $outputDom->documentURI = $documentURI;
         }
         if ($xsltResultDoc->doctype->systemId != "about:legacy-compat") {
             if (!$xsltResultDoc->validate()) {
                 throw new \Exception("DTD validation errors");
             }
         }
         unset($xsltResultDoc, $xsltResultXml, $schemasPath, $p);
         //тут можно и прочую статистику по внутренностям добавить
         header("X-XML-Output-tryHTML: HTML=" . ($html ? "TRUE" : "FALSE") . " XSLTtime=" . sprintf("%0.4f", $xsltStop - $xsltStart) . "s size=" . mb_strlen($data, "ASCII") . "/" . mb_strlen($xsltResult, "ASCII"));
     }
     unset($outputDom);
     //сообщаем клиенту что контент различен в зависимости от заголовка Accept - для кэширования нужно
     header("Vary: Accept");
     if ($html) {
         unset($data);
         header("Content-type: {$htmlContentType};charset=UTF-8");
         //header("Content-Length: ".mb_strlen($xsltResult,"ASCII"));
         //результат - сборная солянка из вывода нескольких скриптов и шаблонов
         //явно кешированием управлять не пытаемся: ставим хедеры на текущее время
         //TODO возможно и хитро проанализировать хедеры всех составляющих и вычислить общий
         //header("Last-Modified: ".gmdate(DATE_RFC1123));
         //header("Etag: Output_".$_SERVER["UNIQUE_ID"]);
         echo $xsltResult;
     } else {
         unset($xsltResult);
         header("Content-type: application/xml");
         //header("Content-Length: ".mb_strlen($data,"ASCII"));
         echo $data;
     }
 }