annotate vendor/behat/mink/src/Selector/NamedSelector.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 7a779792577d
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Mink package.
Chris@0 5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
Chris@0 6 *
Chris@0 7 * For the full copyright and license information, please view the LICENSE
Chris@0 8 * file that was distributed with this source code.
Chris@0 9 */
Chris@0 10
Chris@0 11 namespace Behat\Mink\Selector;
Chris@0 12
Chris@0 13 use Behat\Mink\Selector\Xpath\Escaper;
Chris@0 14
Chris@0 15 /**
Chris@0 16 * Named selectors engine. Uses registered XPath selectors to create new expressions.
Chris@0 17 *
Chris@0 18 * @author Konstantin Kudryashov <ever.zet@gmail.com>
Chris@0 19 */
Chris@0 20 class NamedSelector implements SelectorInterface
Chris@0 21 {
Chris@0 22 private $replacements = array(
Chris@0 23 // simple replacements
Chris@0 24 '%lowercaseType%' => "translate(./@type, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')",
Chris@0 25 '%lowercaseRole%' => "translate(./@role, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')",
Chris@0 26 '%tagTextMatch%' => 'contains(normalize-space(string(.)), %locator%)',
Chris@0 27 '%labelTextMatch%' => './@id = //label[%tagTextMatch%]/@for',
Chris@0 28 '%idMatch%' => './@id = %locator%',
Chris@0 29 '%valueMatch%' => 'contains(./@value, %locator%)',
Chris@0 30 '%idOrValueMatch%' => '(%idMatch% or %valueMatch%)',
Chris@0 31 '%idOrNameMatch%' => '(%idMatch% or ./@name = %locator%)',
Chris@0 32 '%placeholderMatch%' => './@placeholder = %locator%',
Chris@0 33 '%titleMatch%' => 'contains(./@title, %locator%)',
Chris@0 34 '%altMatch%' => 'contains(./@alt, %locator%)',
Chris@0 35 '%relMatch%' => 'contains(./@rel, %locator%)',
Chris@0 36 '%labelAttributeMatch%' => 'contains(./@label, %locator%)',
Chris@0 37
Chris@0 38 // complex replacements
Chris@0 39 '%inputTypeWithoutPlaceholderFilter%' => "%lowercaseType% = 'radio' or %lowercaseType% = 'checkbox' or %lowercaseType% = 'file'",
Chris@0 40 '%fieldFilterWithPlaceholder%' => 'self::input[not(%inputTypeWithoutPlaceholderFilter%)] | self::textarea',
Chris@0 41 '%fieldMatchWithPlaceholder%' => '(%idOrNameMatch% or %labelTextMatch% or %placeholderMatch%)',
Chris@0 42 '%fieldMatchWithoutPlaceholder%' => '(%idOrNameMatch% or %labelTextMatch%)',
Chris@0 43 '%fieldFilterWithoutPlaceholder%' => 'self::input[%inputTypeWithoutPlaceholderFilter%] | self::select',
Chris@0 44 '%buttonTypeFilter%' => "%lowercaseType% = 'submit' or %lowercaseType% = 'image' or %lowercaseType% = 'button' or %lowercaseType% = 'reset'",
Chris@0 45 '%notFieldTypeFilter%' => "not(%buttonTypeFilter% or %lowercaseType% = 'hidden')",
Chris@0 46 '%buttonMatch%' => '%idOrNameMatch% or %valueMatch% or %titleMatch%',
Chris@0 47 '%linkMatch%' => '(%idMatch% or %tagTextMatch% or %titleMatch% or %relMatch%)',
Chris@0 48 '%imgAltMatch%' => './/img[%altMatch%]',
Chris@0 49 );
Chris@0 50
Chris@0 51 private $selectors = array(
Chris@0 52 'fieldset' => <<<XPATH
Chris@0 53 .//fieldset
Chris@0 54 [(%idMatch% or .//legend[%tagTextMatch%])]
Chris@0 55 XPATH
Chris@0 56
Chris@0 57 ,'field' => <<<XPATH
Chris@0 58 .//*
Chris@0 59 [%fieldFilterWithPlaceholder%][%notFieldTypeFilter%][%fieldMatchWithPlaceholder%]
Chris@0 60 |
Chris@0 61 .//label[%tagTextMatch%]//.//*[%fieldFilterWithPlaceholder%][%notFieldTypeFilter%]
Chris@0 62 |
Chris@0 63 .//*
Chris@0 64 [%fieldFilterWithoutPlaceholder%][%notFieldTypeFilter%][%fieldMatchWithoutPlaceholder%]
Chris@0 65 |
Chris@0 66 .//label[%tagTextMatch%]//.//*[%fieldFilterWithoutPlaceholder%][%notFieldTypeFilter%]
Chris@0 67 XPATH
Chris@0 68
Chris@0 69 ,'link' => <<<XPATH
Chris@0 70 .//a
Chris@0 71 [./@href][(%linkMatch% or %imgAltMatch%)]
Chris@0 72 |
Chris@0 73 .//*
Chris@0 74 [%lowercaseRole% = 'link'][(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]
Chris@0 75 XPATH
Chris@0 76
Chris@0 77 ,'button' => <<<XPATH
Chris@0 78 .//input
Chris@0 79 [%buttonTypeFilter%][(%buttonMatch%)]
Chris@0 80 |
Chris@0 81 .//input
Chris@0 82 [%lowercaseType% = 'image'][%altMatch%]
Chris@0 83 |
Chris@0 84 .//button
Chris@0 85 [(%buttonMatch% or %tagTextMatch%)]
Chris@0 86 |
Chris@0 87 .//*
Chris@0 88 [%lowercaseRole% = 'button'][(%buttonMatch% or %tagTextMatch%)]
Chris@0 89 XPATH
Chris@0 90
Chris@0 91 ,'link_or_button' => <<<XPATH
Chris@0 92 .//a
Chris@0 93 [./@href][(%linkMatch% or %imgAltMatch%)]
Chris@0 94 |
Chris@0 95 .//input
Chris@0 96 [%buttonTypeFilter%][(%idOrValueMatch% or %titleMatch%)]
Chris@0 97 |
Chris@0 98 .//input
Chris@0 99 [%lowercaseType% = 'image'][%altMatch%]
Chris@0 100 |
Chris@0 101 .//button
Chris@0 102 [(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]
Chris@0 103 |
Chris@0 104 .//*
Chris@0 105 [(%lowercaseRole% = 'button' or %lowercaseRole% = 'link')][(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]
Chris@0 106 XPATH
Chris@0 107
Chris@0 108 ,'content' => <<<XPATH
Chris@0 109 ./descendant-or-self::*
Chris@0 110 [%tagTextMatch%]
Chris@0 111 XPATH
Chris@0 112
Chris@0 113 ,'select' => <<<XPATH
Chris@0 114 .//select
Chris@0 115 [%fieldMatchWithoutPlaceholder%]
Chris@0 116 |
Chris@0 117 .//label[%tagTextMatch%]//.//select
Chris@0 118 XPATH
Chris@0 119
Chris@0 120 ,'checkbox' => <<<XPATH
Chris@0 121 .//input
Chris@0 122 [%lowercaseType% = 'checkbox'][%fieldMatchWithoutPlaceholder%]
Chris@0 123 |
Chris@0 124 .//label[%tagTextMatch%]//.//input[%lowercaseType% = 'checkbox']
Chris@0 125 XPATH
Chris@0 126
Chris@0 127 ,'radio' => <<<XPATH
Chris@0 128 .//input
Chris@0 129 [%lowercaseType% = 'radio'][%fieldMatchWithoutPlaceholder%]
Chris@0 130 |
Chris@0 131 .//label[%tagTextMatch%]//.//input[%lowercaseType% = 'radio']
Chris@0 132 XPATH
Chris@0 133
Chris@0 134 ,'file' => <<<XPATH
Chris@0 135 .//input
Chris@0 136 [%lowercaseType% = 'file'][%fieldMatchWithoutPlaceholder%]
Chris@0 137 |
Chris@0 138 .//label[%tagTextMatch%]//.//input[%lowercaseType% = 'file']
Chris@0 139 XPATH
Chris@0 140
Chris@0 141 ,'optgroup' => <<<XPATH
Chris@0 142 .//optgroup
Chris@0 143 [%labelAttributeMatch%]
Chris@0 144 XPATH
Chris@0 145
Chris@0 146 ,'option' => <<<XPATH
Chris@0 147 .//option
Chris@0 148 [(./@value = %locator% or %tagTextMatch%)]
Chris@0 149 XPATH
Chris@0 150
Chris@0 151 ,'table' => <<<XPATH
Chris@0 152 .//table
Chris@0 153 [(%idMatch% or .//caption[%tagTextMatch%])]
Chris@0 154 XPATH
Chris@0 155 ,'id' => <<<XPATH
Chris@0 156 .//*[%idMatch%]
Chris@0 157 XPATH
Chris@0 158 ,'id_or_name' => <<<XPATH
Chris@0 159 .//*[%idOrNameMatch%]
Chris@0 160 XPATH
Chris@0 161 );
Chris@0 162 private $xpathEscaper;
Chris@0 163
Chris@0 164 /**
Chris@0 165 * Creates selector instance.
Chris@0 166 */
Chris@0 167 public function __construct()
Chris@0 168 {
Chris@0 169 $this->xpathEscaper = new Escaper();
Chris@0 170
Chris@0 171 foreach ($this->replacements as $from => $to) {
Chris@0 172 $this->replacements[$from] = strtr($to, $this->replacements);
Chris@0 173 }
Chris@0 174
Chris@0 175 foreach ($this->selectors as $alias => $selector) {
Chris@0 176 $this->selectors[$alias] = strtr($selector, $this->replacements);
Chris@0 177 }
Chris@0 178 }
Chris@0 179
Chris@0 180 /**
Chris@0 181 * Registers new XPath selector with specified name.
Chris@0 182 *
Chris@0 183 * @param string $name name for selector
Chris@0 184 * @param string $xpath xpath expression
Chris@0 185 */
Chris@0 186 public function registerNamedXpath($name, $xpath)
Chris@0 187 {
Chris@0 188 $this->selectors[$name] = $xpath;
Chris@0 189 }
Chris@0 190
Chris@0 191 /**
Chris@0 192 * Translates provided locator into XPath.
Chris@0 193 *
Chris@0 194 * @param string|array $locator selector name or array of (selector_name, locator)
Chris@0 195 *
Chris@0 196 * @return string
Chris@0 197 *
Chris@0 198 * @throws \InvalidArgumentException
Chris@0 199 */
Chris@0 200 public function translateToXPath($locator)
Chris@0 201 {
Chris@12 202 if (\is_array($locator)) {
Chris@12 203 if (2 !== \count($locator)) {
Chris@12 204 throw new \InvalidArgumentException('NamedSelector expects array(name, locator) as argument');
Chris@12 205 }
Chris@0 206
Chris@0 207 $selector = $locator[0];
Chris@0 208 $locator = $locator[1];
Chris@0 209 } else {
Chris@0 210 $selector = (string) $locator;
Chris@0 211 $locator = null;
Chris@0 212 }
Chris@0 213
Chris@0 214 if (!isset($this->selectors[$selector])) {
Chris@0 215 throw new \InvalidArgumentException(sprintf(
Chris@0 216 'Unknown named selector provided: "%s". Expected one of (%s)',
Chris@0 217 $selector,
Chris@0 218 implode(', ', array_keys($this->selectors))
Chris@0 219 ));
Chris@0 220 }
Chris@0 221
Chris@0 222 $xpath = $this->selectors[$selector];
Chris@0 223
Chris@0 224 if (null !== $locator) {
Chris@0 225 $xpath = strtr($xpath, array('%locator%' => $this->escapeLocator($locator)));
Chris@0 226 }
Chris@0 227
Chris@0 228 return $xpath;
Chris@0 229 }
Chris@0 230
Chris@0 231 /**
Chris@0 232 * Registers a replacement in the list of replacements.
Chris@0 233 *
Chris@0 234 * This method must be called in the constructor before calling the parent constructor.
Chris@0 235 *
Chris@0 236 * @param string $from
Chris@0 237 * @param string $to
Chris@0 238 */
Chris@0 239 protected function registerReplacement($from, $to)
Chris@0 240 {
Chris@0 241 $this->replacements[$from] = $to;
Chris@0 242 }
Chris@0 243
Chris@0 244 private function escapeLocator($locator)
Chris@0 245 {
Chris@0 246 // If the locator looks like an escaped one, don't escape it again for BC reasons.
Chris@0 247 if (
Chris@0 248 preg_match('/^\'[^\']*+\'$/', $locator)
Chris@0 249 || (false !== strpos($locator, '\'') && preg_match('/^"[^"]*+"$/', $locator))
Chris@0 250 || ((8 < $length = strlen($locator)) && 'concat(' === substr($locator, 0, 7) && ')' === $locator[$length - 1])
Chris@0 251 ) {
Chris@0 252 @trigger_error(
Chris@0 253 'Passing an escaped locator to the named selector is deprecated as of 1.7 and will be removed in 2.0.'
Chris@0 254 .' Pass the raw value instead.',
Chris@0 255 E_USER_DEPRECATED
Chris@0 256 );
Chris@0 257
Chris@0 258 return $locator;
Chris@0 259 }
Chris@0 260
Chris@0 261 return $this->xpathEscaper->escapeLiteral($locator);
Chris@0 262 }
Chris@0 263 }