Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\CssSelector\XPath\Extension; Chris@0: Chris@0: use Symfony\Component\CssSelector\Exception\ExpressionErrorException; Chris@0: use Symfony\Component\CssSelector\Node\FunctionNode; Chris@0: use Symfony\Component\CssSelector\XPath\Translator; Chris@0: use Symfony\Component\CssSelector\XPath\XPathExpr; Chris@0: Chris@0: /** Chris@0: * XPath expression translator HTML extension. Chris@0: * Chris@0: * This component is a port of the Python cssselect library, Chris@0: * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. Chris@0: * Chris@0: * @author Jean-François Simon Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: class HtmlExtension extends AbstractExtension Chris@0: { Chris@0: public function __construct(Translator $translator) Chris@0: { Chris@0: $translator Chris@0: ->getExtension('node') Chris@0: ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) Chris@0: ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getPseudoClassTranslators() Chris@0: { Chris@17: return [ Chris@17: 'checked' => [$this, 'translateChecked'], Chris@17: 'link' => [$this, 'translateLink'], Chris@17: 'disabled' => [$this, 'translateDisabled'], Chris@17: 'enabled' => [$this, 'translateEnabled'], Chris@17: 'selected' => [$this, 'translateSelected'], Chris@17: 'invalid' => [$this, 'translateInvalid'], Chris@17: 'hover' => [$this, 'translateHover'], Chris@17: 'visited' => [$this, 'translateVisited'], Chris@17: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getFunctionTranslators() Chris@0: { Chris@17: return [ Chris@17: 'lang' => [$this, 'translateLang'], Chris@17: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateChecked(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition( Chris@0: '(@checked ' Chris@0: ."and (name(.) = 'input' or name(.) = 'command')" Chris@0: ."and (@type = 'checkbox' or @type = 'radio'))" Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateLink(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateDisabled(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition( Chris@0: '(' Chris@0: .'@disabled and' Chris@0: .'(' Chris@0: ."(name(.) = 'input' and @type != 'hidden')" Chris@0: ." or name(.) = 'button'" Chris@0: ." or name(.) = 'select'" Chris@0: ." or name(.) = 'textarea'" Chris@0: ." or name(.) = 'command'" Chris@0: ." or name(.) = 'fieldset'" Chris@0: ." or name(.) = 'optgroup'" Chris@0: ." or name(.) = 'option'" Chris@0: .')' Chris@0: .') or (' Chris@0: ."(name(.) = 'input' and @type != 'hidden')" Chris@0: ." or name(.) = 'button'" Chris@0: ." or name(.) = 'select'" Chris@0: ." or name(.) = 'textarea'" Chris@0: .')' Chris@0: .' and ancestor::fieldset[@disabled]' Chris@0: ); Chris@0: // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any." Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateEnabled(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition( Chris@0: '(' Chris@0: .'@href and (' Chris@0: ."name(.) = 'a'" Chris@0: ." or name(.) = 'link'" Chris@0: ." or name(.) = 'area'" Chris@0: .')' Chris@0: .') or (' Chris@0: .'(' Chris@0: ."name(.) = 'command'" Chris@0: ." or name(.) = 'fieldset'" Chris@0: ." or name(.) = 'optgroup'" Chris@0: .')' Chris@0: .' and not(@disabled)' Chris@0: .') or (' Chris@0: .'(' Chris@0: ."(name(.) = 'input' and @type != 'hidden')" Chris@0: ." or name(.) = 'button'" Chris@0: ." or name(.) = 'select'" Chris@0: ." or name(.) = 'textarea'" Chris@0: ." or name(.) = 'keygen'" Chris@0: .')' Chris@0: .' and not (@disabled or ancestor::fieldset[@disabled])' Chris@0: .') or (' Chris@0: ."name(.) = 'option' and not(" Chris@0: .'@disabled or ancestor::optgroup[@disabled]' Chris@0: .')' Chris@0: .')' Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: * Chris@0: * @throws ExpressionErrorException Chris@0: */ Chris@0: public function translateLang(XPathExpr $xpath, FunctionNode $function) Chris@0: { Chris@0: $arguments = $function->getArguments(); Chris@0: foreach ($arguments as $token) { Chris@0: if (!($token->isString() || $token->isIdentifier())) { Chris@17: throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); Chris@0: } Chris@0: } Chris@0: Chris@0: return $xpath->addCondition(sprintf( Chris@0: 'ancestor-or-self::*[@lang][1][starts-with(concat(' Chris@0: ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" Chris@0: .', %s)]', Chris@0: 'lang', Chris@0: Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') Chris@0: )); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateSelected(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition("(@selected and name(.) = 'option')"); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateInvalid(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition('0'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateHover(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition('0'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateVisited(XPathExpr $xpath) Chris@0: { Chris@0: return $xpath->addCondition('0'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName() Chris@0: { Chris@0: return 'html'; Chris@0: } Chris@0: }