/** * Generate an XSL stylesheet based on given rendering configuration * * @param Rendering $rendering * @return string */ public function getXSL(Rendering $rendering) { $groupedTemplates = []; $prefixes = []; $templates = $rendering->getTemplates(); // Replace simple templates if there are at least 3 of them TemplateHelper::replaceHomogeneousTemplates($templates, 3); // Group tags with identical templates together foreach ($templates as $tagName => $template) { $template = $this->optimizer->optimizeTemplate($template); $groupedTemplates[$template][] = $tagName; // Record the tag's prefix if applicable $pos = strpos($tagName, ':'); if ($pos !== false) { $prefixes[substr($tagName, 0, $pos)] = 1; } } // Declare all the namespaces in use at the top $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'; // Append the namespace declarations to the stylesheet $prefixes = array_keys($prefixes); sort($prefixes); foreach ($prefixes as $prefix) { $xsl .= ' xmlns:' . $prefix . '="urn:s9e:TextFormatter:' . $prefix . '"'; } /** * Exclude those prefixes to keep the HTML neat * * @link http://lenzconsulting.com/namespaces-in-xslt/#exclude-result-prefixes */ if (!empty($prefixes)) { $xsl .= ' exclude-result-prefixes="' . implode(' ', $prefixes) . '"'; } // Start the stylesheet with the boilerplate stuff $xsl .= '><xsl:output method="html" encoding="utf-8" indent="no"'; $xsl .= '/>'; // Add stylesheet parameters foreach ($rendering->getAllParameters() as $paramName => $paramValue) { $xsl .= '<xsl:param name="' . htmlspecialchars($paramName) . '"'; if ($paramValue === '') { $xsl .= '/>'; } else { $xsl .= '>' . htmlspecialchars($paramValue) . '</xsl:param>'; } } // Add templates foreach ($groupedTemplates as $template => $tagNames) { // Open the template element $xsl .= '<xsl:template match="' . implode('|', $tagNames) . '"'; // Make it a self-closing element if the template is empty if ($template === '') { $xsl .= '/>'; } else { $xsl .= '>' . $template . '</xsl:template>'; } } $xsl .= '</xsl:stylesheet>'; return $xsl; }
public function getXSL(Rendering $rendering) { $groupedTemplates = array(); $prefixes = array(); $templates = $rendering->getTemplates(); TemplateHelper::replaceHomogeneousTemplates($templates, 3); foreach ($templates as $tagName => $template) { $template = $this->optimizer->optimizeTemplate($template); $groupedTemplates[$template][] = $tagName; $pos = \strpos($tagName, ':'); if ($pos !== \false) { $prefixes[\substr($tagName, 0, $pos)] = 1; } } $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'; $prefixes = \array_keys($prefixes); \sort($prefixes); foreach ($prefixes as $prefix) { $xsl .= ' xmlns:' . $prefix . '="urn:s9e:TextFormatter:' . $prefix . '"'; } if (!empty($prefixes)) { $xsl .= ' exclude-result-prefixes="' . \implode(' ', $prefixes) . '"'; } $xsl .= '><xsl:output method="html" encoding="utf-8" indent="no"'; $xsl .= '/>'; foreach ($rendering->getAllParameters() as $paramName => $paramValue) { $xsl .= '<xsl:param name="' . \htmlspecialchars($paramName) . '"'; if ($paramValue === '') { $xsl .= '/>'; } else { $xsl .= '>' . \htmlspecialchars($paramValue) . '</xsl:param>'; } } foreach ($groupedTemplates as $template => $tagNames) { $xsl .= '<xsl:template match="' . \implode('|', $tagNames) . '"'; if ($template === '') { $xsl .= '/>'; } else { $xsl .= '>' . $template . '</xsl:template>'; } } $xsl .= '</xsl:stylesheet>'; return $xsl; }
/** * Generate the source for a PHP class that renders an intermediate representation according to * given rendering configuration * * @param Rendering $rendering * @return string */ public function generate(Rendering $rendering) { // Copy some options to the serializer $this->serializer->useMultibyteStringFunctions = $this->useMultibyteStringFunctions; // Gather templates and optimize simple templates $templates = $rendering->getTemplates(); // Group templates by content to deduplicate them $groupedTemplates = []; foreach ($templates as $tagName => $template) { $groupedTemplates[$template][] = $tagName; } // Record whether the template has a <xsl:apply-templates/> with a select attribute $hasApplyTemplatesSelect = false; // Assign a branch number to each unique template and record the value for each tag $tagBranch = 0; $tagBranches = []; // Store the compiled template for each branch $compiledTemplates = []; // Other branch tables $branchTables = []; // Parse each template and serialize it to PHP foreach ($groupedTemplates as $template => $tagNames) { // Parse the template $ir = TemplateParser::parse($template); // Test whether this template uses an <xsl:apply-templates/> element with a select if (!$hasApplyTemplatesSelect) { foreach ($ir->getElementsByTagName('applyTemplates') as $applyTemplates) { if ($applyTemplates->hasAttribute('select')) { $hasApplyTemplatesSelect = true; } } } // Serialize the representation to PHP $templateSource = $this->serializer->serialize($ir->documentElement); if (isset($this->optimizer)) { $templateSource = $this->optimizer->optimize($templateSource); } // Record the branch tables used in this template $branchTables += $this->serializer->branchTables; // Record the source for this branch number and assign this number to each tag $compiledTemplates[$tagBranch] = $templateSource; foreach ($tagNames as $tagName) { $tagBranches[$tagName] = $tagBranch; } ++$tagBranch; } // Unset the vars we don't need anymore unset($groupedTemplates, $ir, $quickRender); // Store the compiled template if we plan to create a Quick renderer $quickSource = false; if ($this->enableQuickRenderer) { $quickRender = []; foreach ($tagBranches as $tagName => $tagBranch) { $quickRender[$tagName] = $compiledTemplates[$tagBranch]; } $quickSource = Quick::getSource($quickRender); unset($quickRender); } // Coalesce all the compiled templates $templatesSource = Quick::generateConditionals('$tb', $compiledTemplates); unset($compiledTemplates); // Test whether any templates needs an XPath engine if ($hasApplyTemplatesSelect) { $needsXPath = true; } elseif (strpos($templatesSource, '$this->getParamAsXPath') !== false) { $needsXPath = true; } elseif (strpos($templatesSource, '$this->xpath') !== false) { $needsXPath = true; } else { $needsXPath = false; } // Start the code right after the class name, we'll prepend the header when we're done $php = []; $php[] = ' extends \\s9e\\TextFormatter\\Renderer'; $php[] = '{'; $php[] = ' protected $params=' . self::export($rendering->getAllParameters()) . ';'; $php[] = ' protected static $tagBranches=' . self::export($tagBranches) . ';'; foreach ($branchTables as $varName => $branchTable) { $php[] = ' protected static $' . $varName . '=' . self::export($branchTable) . ';'; } if ($needsXPath) { $php[] = ' protected $xpath;'; } $php[] = ' public function __sleep()'; $php[] = ' {'; $php[] = ' $props = get_object_vars($this);'; $php[] = "\t\tunset(\$props['out'], \$props['proc'], \$props['source']" . ($needsXPath ? ", \$props['xpath']" : '') . ');'; $php[] = ' return array_keys($props);'; $php[] = ' }'; $php[] = ' public function renderRichText($xml)'; $php[] = ' {'; if ($quickSource !== false) { // Try the Quick renderer first and if anything happens just keep going with the normal // rendering $php[] = ' if (!isset($this->quickRenderingTest) || !preg_match($this->quickRenderingTest, $xml))'; $php[] = ' {'; $php[] = ' try'; $php[] = ' {'; $php[] = ' return $this->renderQuick($xml);'; $php[] = ' }'; $php[] = ' catch (\\Exception $e)'; $php[] = ' {'; $php[] = ' }'; $php[] = ' }'; } $php[] = ' $dom = $this->loadXML($xml);'; if ($needsXPath) { $php[] = ' $this->xpath = new \\DOMXPath($dom);'; } $php[] = "\t\t\$this->out = '';"; $php[] = ' $this->at($dom->documentElement);'; if ($needsXPath) { $php[] = ' $this->xpath = null;'; } $php[] = ' return $this->out;'; $php[] = ' }'; if ($hasApplyTemplatesSelect) { $php[] = ' protected function at(\\DOMNode $root, $xpath = null)'; } else { $php[] = ' protected function at(\\DOMNode $root)'; } $php[] = ' {'; $php[] = ' if ($root->nodeType === 3)'; $php[] = ' {'; $php[] = ' $this->out .= htmlspecialchars($root->textContent,' . ENT_NOQUOTES . ');'; $php[] = ' }'; $php[] = ' else'; $php[] = ' {'; if ($hasApplyTemplatesSelect) { $php[] = ' foreach (isset($xpath) ? $this->xpath->query($xpath, $root) : $root->childNodes as $node)'; } else { $php[] = ' foreach ($root->childNodes as $node)'; } $php[] = ' {'; $php[] = ' if (!isset(self::$tagBranches[$node->nodeName]))'; $php[] = ' {'; $php[] = ' $this->at($node);'; $php[] = ' }'; $php[] = ' else'; $php[] = ' {'; $php[] = ' $tb = self::$tagBranches[$node->nodeName];'; $php[] = ' ' . $templatesSource; $php[] = ' }'; $php[] = ' }'; $php[] = ' }'; $php[] = ' }'; // Add the getParamAsXPath() method if necessary if (strpos($templatesSource, '$this->getParamAsXPath') !== false) { $php[] = ' protected function getParamAsXPath($k)'; $php[] = ' {'; $php[] = ' if (!isset($this->params[$k]))'; $php[] = ' {'; $php[] = ' return "\'\'";'; $php[] = ' }'; $php[] = ' $str = $this->params[$k];'; $php[] = ' if (strpos($str, "\'") === false)'; $php[] = ' {'; $php[] = ' return "\'$str\'";'; $php[] = ' }'; $php[] = ' if (strpos($str, \'"\') === false)'; $php[] = ' {'; $php[] = ' return "\\"$str\\"";'; $php[] = ' }'; $php[] = ' $toks = [];'; $php[] = ' $c = \'"\';'; $php[] = ' $pos = 0;'; $php[] = ' while ($pos < strlen($str))'; $php[] = ' {'; $php[] = ' $spn = strcspn($str, $c, $pos);'; $php[] = ' if ($spn)'; $php[] = ' {'; $php[] = ' $toks[] = $c . substr($str, $pos, $spn) . $c;'; $php[] = ' $pos += $spn;'; $php[] = ' }'; $php[] = ' $c = ($c === \'"\') ? "\'" : \'"\';'; $php[] = ' }'; $php[] = ' return \'concat(\' . implode(\',\', $toks) . \')\';'; $php[] = ' }'; } // Append the Quick renderer if applicable if ($quickSource !== false) { $php[] = $quickSource; } // Close the class definition $php[] = '}'; // Assemble the source $php = implode("\n", $php); // Finally, optimize the control structures if (isset($this->controlStructuresOptimizer)) { $php = $this->controlStructuresOptimizer->optimize($php); } // Generate a name for that class if necessary, and save it $className = isset($this->className) ? $this->className : $this->defaultClassPrefix . sha1($php); $this->lastClassName = $className; // Prepare the header $header = "\n" . "/**\n" . "* @package s9e\\TextFormatter\n" . "* @copyright Copyright (c) 2010-2015 The s9e Authors\n" . "* @license http://www.opensource.org/licenses/mit-license.php The MIT License\n" . "*/\n"; // Declare the namespace and class name $pos = strrpos($className, '\\'); if ($pos !== false) { $header .= 'namespace ' . substr($className, 0, $pos) . ";\n\n"; $className = substr($className, 1 + $pos); } // Prepend the header and the class name $php = $header . 'class ' . $className . $php; return $php; }
public function generate(Rendering $rendering) { $this->serializer->useMultibyteStringFunctions = $this->useMultibyteStringFunctions; $templates = $rendering->getTemplates(); $groupedTemplates = array(); foreach ($templates as $tagName => $template) { $groupedTemplates[$template][] = $tagName; } $hasApplyTemplatesSelect = \false; $tagBranch = 0; $tagBranches = array(); $compiledTemplates = array(); $branchTables = array(); foreach ($groupedTemplates as $template => $tagNames) { $ir = TemplateParser::parse($template); if (!$hasApplyTemplatesSelect) { foreach ($ir->getElementsByTagName('applyTemplates') as $applyTemplates) { if ($applyTemplates->hasAttribute('select')) { $hasApplyTemplatesSelect = \true; } } } $templateSource = $this->serializer->serialize($ir->documentElement); if (isset($this->optimizer)) { $templateSource = $this->optimizer->optimize($templateSource); } $branchTables += $this->serializer->branchTables; $compiledTemplates[$tagBranch] = $templateSource; foreach ($tagNames as $tagName) { $tagBranches[$tagName] = $tagBranch; } ++$tagBranch; } unset($groupedTemplates, $ir, $quickRender); $quickSource = \false; if ($this->enableQuickRenderer) { $quickRender = array(); foreach ($tagBranches as $tagName => $tagBranch) { $quickRender[$tagName] = $compiledTemplates[$tagBranch]; } $quickSource = Quick::getSource($quickRender); unset($quickRender); } $templatesSource = Quick::generateConditionals('$tb', $compiledTemplates); unset($compiledTemplates); if ($hasApplyTemplatesSelect) { $needsXPath = \true; } elseif (\strpos($templatesSource, '$this->getParamAsXPath') !== \false) { $needsXPath = \true; } elseif (\strpos($templatesSource, '$this->xpath') !== \false) { $needsXPath = \true; } else { $needsXPath = \false; } $php = array(); $php[] = ' extends \\s9e\\TextFormatter\\Renderer'; $php[] = '{'; $php[] = ' protected $params=' . self::export($rendering->getAllParameters()) . ';'; $php[] = ' protected static $tagBranches=' . self::export($tagBranches) . ';'; foreach ($branchTables as $varName => $branchTable) { $php[] = ' protected static $' . $varName . '=' . self::export($branchTable) . ';'; } if ($needsXPath) { $php[] = ' protected $xpath;'; } $php[] = ' public function __sleep()'; $php[] = ' {'; $php[] = ' $props = get_object_vars($this);'; $php[] = "\t\tunset(\$props['out'], \$props['proc'], \$props['source']" . ($needsXPath ? ", \$props['xpath']" : '') . ');'; $php[] = ' return array_keys($props);'; $php[] = ' }'; $php[] = ' public function renderRichText($xml)'; $php[] = ' {'; if ($quickSource !== \false) { $php[] = ' if (!isset($this->quickRenderingTest) || !preg_match($this->quickRenderingTest, $xml))'; $php[] = ' {'; $php[] = ' try'; $php[] = ' {'; $php[] = ' return $this->renderQuick($xml);'; $php[] = ' }'; $php[] = ' catch (\\Exception $e)'; $php[] = ' {'; $php[] = ' }'; $php[] = ' }'; } $php[] = ' $dom = $this->loadXML($xml);'; if ($needsXPath) { $php[] = ' $this->xpath = new \\DOMXPath($dom);'; } $php[] = "\t\t\$this->out = '';"; $php[] = ' $this->at($dom->documentElement);'; if ($needsXPath) { $php[] = ' $this->xpath = null;'; } $php[] = ' return $this->out;'; $php[] = ' }'; if ($hasApplyTemplatesSelect) { $php[] = ' protected function at(\\DOMNode $root, $xpath = null)'; } else { $php[] = ' protected function at(\\DOMNode $root)'; } $php[] = ' {'; $php[] = ' if ($root->nodeType === 3)'; $php[] = ' {'; $php[] = ' $this->out .= htmlspecialchars($root->textContent,' . \ENT_NOQUOTES . ');'; $php[] = ' }'; $php[] = ' else'; $php[] = ' {'; if ($hasApplyTemplatesSelect) { $php[] = ' foreach (isset($xpath) ? $this->xpath->query($xpath, $root) : $root->childNodes as $node)'; } else { $php[] = ' foreach ($root->childNodes as $node)'; } $php[] = ' {'; $php[] = ' if (!isset(self::$tagBranches[$node->nodeName]))'; $php[] = ' {'; $php[] = ' $this->at($node);'; $php[] = ' }'; $php[] = ' else'; $php[] = ' {'; $php[] = ' $tb = self::$tagBranches[$node->nodeName];'; $php[] = ' ' . $templatesSource; $php[] = ' }'; $php[] = ' }'; $php[] = ' }'; $php[] = ' }'; if (\strpos($templatesSource, '$this->getParamAsXPath') !== \false) { $php[] = ' protected function getParamAsXPath($k)'; $php[] = ' {'; $php[] = ' if (!isset($this->params[$k]))'; $php[] = ' {'; $php[] = ' return "\'\'";'; $php[] = ' }'; $php[] = ' $str = $this->params[$k];'; $php[] = ' if (strpos($str, "\'") === false)'; $php[] = ' {'; $php[] = ' return "\'$str\'";'; $php[] = ' }'; $php[] = ' if (strpos($str, \'"\') === false)'; $php[] = ' {'; $php[] = ' return "\\"$str\\"";'; $php[] = ' }'; $php[] = ' $toks = array();'; $php[] = ' $c = \'"\';'; $php[] = ' $pos = 0;'; $php[] = ' while ($pos < strlen($str))'; $php[] = ' {'; $php[] = ' $spn = strcspn($str, $c, $pos);'; $php[] = ' if ($spn)'; $php[] = ' {'; $php[] = ' $toks[] = $c . substr($str, $pos, $spn) . $c;'; $php[] = ' $pos += $spn;'; $php[] = ' }'; $php[] = ' $c = ($c === \'"\') ? "\'" : \'"\';'; $php[] = ' }'; $php[] = ' return \'concat(\' . implode(\',\', $toks) . \')\';'; $php[] = ' }'; } if ($quickSource !== \false) { $php[] = $quickSource; } $php[] = '}'; $php = \implode("\n", $php); if (isset($this->controlStructuresOptimizer)) { $php = $this->controlStructuresOptimizer->optimize($php); } $className = isset($this->className) ? $this->className : $this->defaultClassPrefix . \sha1($php); $this->lastClassName = $className; $header = "\n/**\n* @package s9e\\TextFormatter\n* @copyright Copyright (c) 2010-2016 The s9e Authors\n* @license http://www.opensource.org/licenses/mit-license.php The MIT License\n*/\n"; $pos = \strrpos($className, '\\'); if ($pos !== \false) { $header .= 'namespace ' . \substr($className, 0, $pos) . ";\n\n"; $className = \substr($className, 1 + $pos); } $php = $header . 'class ' . $className . $php; return $php; }