<?php
namespace phpDocumentor\Plugin\Core\Transformer\Writer;
use Monolog\Logger;
use phpDocumentor\Application;
use phpDocumentor\Descriptor\ProjectDescriptor;
use phpDocumentor\Event\Dispatcher;
use phpDocumentor\Plugin\Core\Exception;
use phpDocumentor\Transformer\Event\PreXslWriterEvent;
use phpDocumentor\Transformer\Router\ForFileProxy;
use phpDocumentor\Transformer\Router\Queue;
use phpDocumentor\Transformer\Transformation;
use phpDocumentor\Transformer\Transformation as TransformationObject;
use phpDocumentor\Transformer\Writer\Exception\RequirementMissing;
use phpDocumentor\Transformer\Writer\Routable;
use phpDocumentor\Transformer\Writer\WriterAbstract;
class Xsl extends WriterAbstract implements Routable
{
protected $logger;
protected $xsl_variables = array();
private $routers;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function checkRequirements()
{
if (!class_exists('XSLTProcessor') && (!extension_loaded('xslcache'))) {
throw new RequirementMissing(
'The XSL writer was unable to find your XSLTProcessor; '
. 'please check if you have installed the PHP XSL extension or XSLCache extension'
);
}
}
public function setRouters(Queue $routers)
{
$this->routers = $routers;
}
public function transform(ProjectDescriptor $project, Transformation $transformation)
{
$structure = $this->loadAst($this->getAstPath($transformation));
$proc = $this->getXslProcessor($transformation);
$proc->registerPHPFunctions();
$this->registerDefaultVariables($transformation, $proc, $structure);
$this->setProcessorParameters($transformation, $proc);
$artifact = $this->getArtifactPath($transformation);
$this->checkForSpacesInPath($artifact);
<var>} with the sluggified node-value of the
if ($transformation->getQuery() !== '') {
$xpath = new \DOMXPath($structure);
$qry = $xpath->query($transformation->getQuery());
$count = $qry->length;
foreach ($qry as $key => $element) {
Dispatcher::getInstance()->dispatch(
'transformer.writer.xsl.pre',
PreXslWriterEvent::createInstance($this)->setElement($element)->setProgress(array($key+1, $count))
);
$proc->setParameter('', $element->nodeName, $element->nodeValue);
$file_name = $transformation->getTransformer()->generateFilename(
$element->nodeValue
);
if (! $artifact) {
$url = $this->generateUrlForXmlElement($project, $element);
if ($url === false || $url[0] !== DIRECTORY_SEPARATOR) {
continue;
}
$filename = $transformation->getTransformer()->getTarget()
. str_replace('/', DIRECTORY_SEPARATOR, $url);
} else {
$filename = str_replace('{$' . $element->nodeName . '}', $file_name, $artifact);
}
$relativeFileName = substr($filename, strlen($transformation->getTransformer()->getTarget()) + 1);
$proc->setParameter('', 'root', str_repeat('../', substr_count($relativeFileName, '/')));
$this->writeToFile($filename, $proc, $structure);
}
} else {
if (substr($transformation->getArtifact(), 0, 1) == '$') {
$variable_name = substr($transformation->getArtifact(), 1);
$this->xsl_variables[$variable_name] = $proc->transformToXml($structure);
} else {
$relativeFileName = substr($artifact, strlen($transformation->getTransformer()->getTarget()) + 1);
$proc->setParameter('', 'root', str_repeat('../', substr_count($relativeFileName, '/')));
$this->writeToFile($artifact, $proc, $structure);
}
}
}
protected function getXsltUriFromFilename($filename)
{
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && ! \Phar::running()) {
$filename = '/' . $filename;
}
return 'file://' . $filename;
}
public function setProcessorParameters(TransformationObject $transformation, $proc)
{
foreach ($this->xsl_variables as $key => $variable) {
if ((strpos($variable, '"') !== false)
&& ((strpos($variable, "'") !== false))
) {
$this->logger->warning(
'XSLT does not allow both double and single quotes in '
. 'a variable; transforming single quotes to a character '
. 'encoded version in variable: ' . $key
);
$variable = str_replace("'", "'", $variable);
}
$proc->setParameter('', $key, $variable);
}
$parameters = $transformation->getParameters();
if (isset($parameters['variables'])) {
foreach ($parameters['variables'] as $key => $value) {
$proc->setParameter('', $key, $value);
}
}
}
protected function getXslProcessor(Transformation $transformation)
{
$xslTemplatePath = $transformation->getSourceAsPath();
$this->logger->debug('Loading XSL template: ' . $xslTemplatePath);
if (!file_exists($xslTemplatePath)) {
throw new Exception('Unable to find XSL template "' . $xslTemplatePath . '"');
}
if (extension_loaded('xslcache')) {
$proc = new \XSLTCache();
$proc->importStyleSheet($xslTemplatePath, true);
return $proc;
} else {
$xsl = new \DOMDocument();
$xsl->load($xslTemplatePath);
$proc = new \XSLTProcessor();
$proc->importStyleSheet($xsl);
return $proc;
}
}
private function loadAst($structureFilename)
{
if (!is_readable($structureFilename)) {
throw new \RuntimeException(
'Structure.xml file was not found in the target directory, is the XML writer missing from the '
. 'template definition?'
);
}
$structure = new \DOMDocument('1.0', 'utf-8');
libxml_use_internal_errors(true);
$structure->load($structureFilename);
if (empty($structure->documentElement)) {
$message = 'Specified DOMDocument lacks documentElement, cannot transform.';
$error = libxml_get_last_error();
if ($error) {
$message .= PHP_EOL . 'Apparently an error occurred with reading the structure.xml file, the reported '
. 'error was "' . trim($error->message) . '" on line ' . $error->line;
}
throw new Exception($message);
}
return $structure;
}
private function registerDefaultVariables(Transformation $transformation, $proc, $structure)
{
$proc->setParameter('', 'title', $structure->documentElement->getAttribute('title'));
if ($transformation->getParameter('search') !== null && $transformation->getParameter('search')->getValue()) {
$proc->setParameter('', 'search_template', $transformation->getParameter('search')->getValue());
} else {
$proc->setParameter('', 'search_template', 'none');
}
$proc->setParameter('', 'version', Application::$VERSION);
$proc->setParameter('', 'generated_datetime', date('r'));
}
private function writeToFile($filename, $proc, $structure)
{
if (!file_exists(dirname($filename))) {
mkdir(dirname($filename), 0755, true);
}
$proc->transformToURI($structure, $this->getXsltUriFromFilename($filename));
}
private function getAstPath(Transformation $transformation)
{
return $transformation->getTransformer()->getTarget() . DIRECTORY_SEPARATOR . 'structure.xml';
}
private function getArtifactPath(Transformation $transformation)
{
return $transformation->getArtifact()
? $transformation->getTransformer()->getTarget() . DIRECTORY_SEPARATOR . $transformation->getArtifact()
: null;
}
private function generateUrlForXmlElement(ProjectDescriptor $project, $element)
{
$elements = $project->getIndexes()->get('elements');
$elementFqcn = ($element->parentNode->nodeName === 'namespace' ? '~\\' : '') . $element->nodeValue;
$node = (isset($elements[$elementFqcn]))
? $elements[$elementFqcn]
: $element->nodeValue;
$rule = $this->routers->match($node);
if (!$rule) {
throw new \InvalidArgumentException(
'No matching routing rule could be found for the given node, please provide an artifact location, '
. 'encountered: ' . ($node === null ? 'NULL' : get_class($node))
);
}
$rule = new ForFileProxy($rule);
$url = $rule->generate($node);
return $url;
}
}