paramValue] */ protected $params = []; /** * @var string Saved locale */ protected $savedLocale = '0'; /** * Create a return a new DOMDocument loaded with given XML * * @param string $xml Source XML * @return DOMDocument */ protected function loadXML($xml) { $this->checkUnsupported($xml); // Activate small nodes allocation and relax LibXML's hardcoded limits if applicable. Limits // on tags can be set during configuration $flags = (LIBXML_VERSION >= 20700) ? LIBXML_COMPACT | LIBXML_PARSEHUGE : 0; $useErrors = libxml_use_internal_errors(true); $dom = new DOMDocument; $success = $dom->loadXML($xml, $flags); libxml_use_internal_errors($useErrors); if (!$success) { throw new InvalidArgumentException('Cannot load XML: ' . libxml_get_last_error()->message); } return $dom; } /** * Render an intermediate representation * * @param string $xml Intermediate representation * @return string Rendered result */ public function render($xml) { if (substr($xml, 0, 3) === '' && substr($xml, -4) === '') { return $this->renderPlainText($xml); } else { return $this->renderRichText(preg_replace('(<[eis]>[^<]*)', '', $xml)); } } /** * Render an intermediate representation of plain text * * @param string $xml Intermediate representation * @return string Rendered result */ protected function renderPlainText($xml) { // Remove the and tags $html = substr($xml, 3, -4); // Replace all
with
$html = str_replace('
', '
', $html); // Decode encoded characters from the Supplementary Multilingual Plane $html = $this->decodeSMP($html); return $html; } /** * Render an intermediate representation of rich text * * @param string $xml Intermediate representation * @return string Rendered result */ abstract protected function renderRichText($xml); /** * Get the value of a parameter * * @param string $paramName * @return string */ public function getParameter($paramName) { return $this->params[$paramName] ?? ''; } /** * Get the values of all parameters * * @return array Associative array of parameter names and values */ public function getParameters() { return $this->params; } /** * Set the value of a parameter from the stylesheet * * @param string $paramName Parameter name * @param mixed $paramValue Parameter's value * @return void */ public function setParameter($paramName, $paramValue) { $this->params[$paramName] = (string) $paramValue; } /** * Set the values of several parameters from the stylesheet * * @param array $params Associative array of [parameter name => parameter value] * @return void */ public function setParameters(array $params) { foreach ($params as $paramName => $paramValue) { $this->setParameter($paramName, $paramValue); } } /** * Test for the presence of unsupported XML and throw an exception if found * * @param string $xml XML * @return void */ protected function checkUnsupported($xml) { if (preg_match('((?<=<)[!?])', $xml, $m)) { $errors = [ '!' => 'DTDs, CDATA nodes and comments are not allowed', '?' => 'Processing instructions are not allowed' ]; throw new InvalidArgumentException($errors[$m[0]]); } } /** * Decode encoded characters from the Supplementary Multilingual Plane * * @param string $str Encoded string * @return string Decoded string */ protected function decodeSMP($str) { if (strpos($str, '&#') === false) { return $str; } return preg_replace_callback('(&#(?:x[0-9A-Fa-f]+|[0-9]+);)', __CLASS__ . '::decodeEntity', $str); } /** * Decode a matched SGML entity * * @param string[] $m Captures from PCRE * @return string Decoded entity */ protected static function decodeEntity(array $m) { return htmlspecialchars(html_entity_decode($m[0], ENT_QUOTES, 'UTF-8'), ENT_COMPAT); } /** * Restore the original locale */ protected function restoreLocale(): void { if ($this->savedLocale !== 'C') { setlocale(LC_NUMERIC, $this->savedLocale); } } /** * Temporarily set the locale to C */ protected function setLocale(): void { $this->savedLocale = setlocale(LC_NUMERIC, '0'); if ($this->savedLocale !== 'C') { setlocale(LC_NUMERIC, 'C'); } } }