/**
  * @testdox parse() tests
  * @dataProvider getParseTests
  */
 public function testParse($attrValue, $expected)
 {
     if ($expected instanceof Exception) {
         $this->setExpectedException(get_class($expected), $expected->getMessage());
     }
     $this->assertSame($expected, AVTHelper::parse($attrValue));
 }
 /**
  * Convert an attribute value template into PHP
  *
  * NOTE: escaping must be performed by the caller
  *
  * @link http://www.w3.org/TR/xslt#dt-attribute-value-template
  *
  * @param  string $attrValue Attribute value template
  * @return string
  */
 protected function convertAttributeValueTemplate($attrValue)
 {
     $phpExpressions = [];
     foreach (AVTHelper::parse($attrValue) as $token) {
         if ($token[0] === 'literal') {
             $phpExpressions[] = var_export($token[1], true);
         } else {
             $phpExpressions[] = $this->convertXPath($token[1]);
         }
     }
     return implode('.', $phpExpressions);
 }
 /**
  * Normalize the value of an attribute
  *
  * @param  DOMAttr $attribute
  * @return void
  */
 protected function normalizeAttribute(DOMAttr $attribute)
 {
     // Trim the URL and parse it
     $tokens = AVTHelper::parse(trim($attribute->value));
     $attrValue = '';
     foreach ($tokens as list($type, $content)) {
         if ($type === 'literal') {
             $attrValue .= BuiltInFilters::sanitizeUrl($content);
         } else {
             $attrValue .= '{' . $content . '}';
         }
     }
     // Unescape brackets in the host part
     $attrValue = $this->unescapeBrackets($attrValue);
     // Update the attribute's value
     $attribute->value = htmlspecialchars($attrValue);
 }
 /**
  * Get all the potential XPath expressions used in given template
  *
  * @param  DOMElement $template <xsl:template/> node
  * @return array                XPath expression as key, reference node as value
  */
 protected function getExpressions(DOMElement $template)
 {
     $xpath = new DOMXPath($template->ownerDocument);
     $exprs = [];
     foreach ($xpath->query('//@*') as $attribute) {
         if ($attribute->parentNode->namespaceURI === self::XMLNS_XSL) {
             // Attribute of an XSL element. May or may not use XPath, but it shouldn't produce
             // false-positives
             $expr = $attribute->value;
             $exprs[$expr] = $attribute;
         } else {
             // Attribute of an HTML (or otherwise) element -- Look for inline expressions
             foreach (AVTHelper::parse($attribute->value) as $token) {
                 if ($token[0] === 'expression') {
                     $exprs[$token[1]] = $attribute;
                 }
             }
         }
     }
     return $exprs;
 }
 protected function generateAttributes(array $attributes, $addResponsive = \false)
 {
     if ($addResponsive) {
         $attributes = $this->addResponsiveStyle($attributes);
     }
     unset($attributes['responsive']);
     $xsl = '';
     foreach ($attributes as $attrName => $innerXML) {
         if (\strpos($innerXML, '<') === \false) {
             $tokens = AVTHelper::parse($innerXML);
             $innerXML = '';
             foreach ($tokens as $_bada9f30) {
                 list($type, $content) = $_bada9f30;
                 if ($type === 'literal') {
                     $innerXML .= \htmlspecialchars($content, \ENT_NOQUOTES, 'UTF-8');
                 } else {
                     $innerXML .= '<xsl:value-of select="' . \htmlspecialchars($content, \ENT_QUOTES, 'UTF-8') . '"/>';
                 }
             }
         }
         $xsl .= '<xsl:attribute name="' . \htmlspecialchars($attrName, \ENT_QUOTES, 'UTF-8') . '">' . $innerXML . '</xsl:attribute>';
     }
     return $xsl;
 }
 /**
  * Append an <output/> element to given node in the IR
  *
  * @param  DOMElement $ir      Parent node
  * @param  string     $type    Either 'avt', 'literal' or 'xpath'
  * @param  string     $content Content to output
  * @return void
  */
 protected static function appendOutput(DOMElement $ir, $type, $content)
 {
     // Reparse AVTs and add them as separate xpath/literal outputs
     if ($type === 'avt') {
         foreach (AVTHelper::parse($content) as $token) {
             $type = $token[0] === 'expression' ? 'xpath' : 'literal';
             self::appendOutput($ir, $type, $token[1]);
         }
         return;
     }
     if ($type === 'xpath') {
         // Remove whitespace surrounding XPath expressions
         $content = trim($content);
     }
     if ($type === 'literal' && $content === '') {
         // Don't add empty literals
         return;
     }
     self::appendElement($ir, 'output', htmlspecialchars($content))->setAttribute('type', $type);
 }
 /**
  * Test whether an attribute node is safe
  *
  * @param  DOMAttr $attribute Attribute node
  * @param  Tag     $tag       Reference tag
  * @return void
  */
 protected function checkAttributeNode(DOMAttr $attribute, Tag $tag)
 {
     // Parse the attribute value for XPath expressions and assess their safety
     foreach (AVTHelper::parse($attribute->value) as $token) {
         if ($token[0] === 'expression') {
             $this->checkExpression($attribute, $token[1], $tag);
         }
     }
 }
 /**
  * Get the regexp used to remove meta elements from the intermediate representation
  *
  * @param  array  $templates
  * @return string
  */
 public static function getMetaElementsRegexp(array $templates)
 {
     $exprs = [];
     // Coalesce all templates and load them into DOM
     $xsl = '<xsl:template xmlns:xsl="http://www.w3.org/1999/XSL/Transform">' . implode('', $templates) . '</xsl:template>';
     $dom = new DOMDocument();
     $dom->loadXML($xsl);
     $xpath = new DOMXPath($dom);
     // Collect the values of all the "match", "select" and "test" attributes of XSL elements
     $query = '//xsl:*/@*[contains("matchselectest", name())]';
     foreach ($xpath->query($query) as $attribute) {
         $exprs[] = $attribute->value;
     }
     // Collect the XPath expressions used in all the attributes of non-XSL elements
     $query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]/@*';
     foreach ($xpath->query($query) as $attribute) {
         foreach (AVTHelper::parse($attribute->value) as $token) {
             if ($token[0] === 'expression') {
                 $exprs[] = $token[1];
             }
         }
     }
     // Names of the meta elements
     $tagNames = ['e' => true, 'i' => true, 's' => true];
     // In the highly unlikely event the meta elements are rendered, we remove them from the list
     foreach (array_keys($tagNames) as $tagName) {
         if (isset($templates[$tagName]) && $templates[$tagName] !== '') {
             unset($tagNames[$tagName]);
         }
     }
     // Create a regexp that matches the tag names used as element names, e.g. "s" in "//s" but
     // not in "@s" or "$s"
     $regexp = '(\\b(?<![$@])(' . implode('|', array_keys($tagNames)) . ')(?!-)\\b)';
     // Now look into all of the expressions that we've collected
     preg_match_all($regexp, implode("\n", $exprs), $m);
     foreach ($m[0] as $tagName) {
         unset($tagNames[$tagName]);
     }
     if (empty($tagNames)) {
         // Always-false regexp
         return '((?!))';
     }
     return '(<' . RegexpBuilder::fromList(array_keys($tagNames)) . '>[^<]*</[^>]+>)';
 }