*/ class NamedSelector implements SelectorInterface { /** @var array */ private $replacements = [ '%lowercaseType%' => 'translate(./@type, \'ABCDEFGHIJKLMNOPQRSTUVWXYZ\', \'abcdefghijklmnopqrstuvwxyz\')', '%lowercaseRole%' => 'translate(./@role, \'ABCDEFGHIJKLMNOPQRSTUVWXYZ\', \'abcdefghijklmnopqrstuvwxyz\')', '%tagTextMatch%' => 'contains(normalize-space(string(.)), %locator%)', '%labelTextMatch%' => './@id = //label[%tagTextMatch%]/@for', '%idMatch%' => './@id = %locator%', '%valueMatch%' => 'contains(./@value, %locator%)', '%idOrValueMatch%' => '(%idMatch% or %valueMatch%)', '%idOrNameMatch%' => '(%idMatch% or ./@name = %locator%)', '%placeholderMatch%' => './@placeholder = %locator%', '%titleMatch%' => 'contains(./@title, %locator%)', '%altMatch%' => 'contains(./@alt, %locator%)', '%relMatch%' => 'contains(./@rel, %locator%)', '%labelAttributeMatch%' => 'contains(./@label, %locator%)', '%inputTypeWithoutPlaceholderFilter%' => '%lowercaseType% = \'radio\' or %lowercaseType% = \'checkbox\' or %lowercaseType% = \'file\'', '%fieldFilterWithPlaceholder%' => 'self::input[not(%inputTypeWithoutPlaceholderFilter%)] | self::textarea', '%fieldMatchWithPlaceholder%' => '(%idOrNameMatch% or %labelTextMatch% or %placeholderMatch%)', '%fieldMatchWithoutPlaceholder%' => '(%idOrNameMatch% or %labelTextMatch%)', '%fieldFilterWithoutPlaceholder%' => 'self::input[%inputTypeWithoutPlaceholderFilter%] | self::select', '%buttonTypeFilter%' => '%lowercaseType% = \'submit\' or %lowercaseType% = \'image\' or %lowercaseType% = \'button\' or %lowercaseType% = \'reset\'', '%notFieldTypeFilter%' => 'not(%buttonTypeFilter% or %lowercaseType% = \'hidden\')', '%buttonMatch%' => '%idOrNameMatch% or %valueMatch% or %titleMatch%', '%linkMatch%' => '(%idMatch% or %tagTextMatch% or %titleMatch% or %relMatch%)', '%imgAltMatch%' => './/img[%altMatch%]', ]; /** @var array */ private $selectors = [ 'fieldset' => ".//fieldset\n[(%idMatch% or .//legend[%tagTextMatch%])]", 'field' => ".//*\n[%fieldFilterWithPlaceholder%][%notFieldTypeFilter%][%fieldMatchWithPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//*[%fieldFilterWithPlaceholder%][%notFieldTypeFilter%]\n|\n.//*\n[%fieldFilterWithoutPlaceholder%][%notFieldTypeFilter%][%fieldMatchWithoutPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//*[%fieldFilterWithoutPlaceholder%][%notFieldTypeFilter%]", 'link' => ".//a\n[./@href][(%linkMatch% or %imgAltMatch%)]\n|\n.//*\n[%lowercaseRole% = 'link'][(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]", 'button' => ".//input\n[%buttonTypeFilter%][(%buttonMatch%)]\n|\n.//input\n[%lowercaseType% = 'image'][%altMatch%]\n|\n.//button\n[(%buttonMatch% or %tagTextMatch%)]\n|\n.//*\n[%lowercaseRole% = 'button'][(%buttonMatch% or %tagTextMatch%)]", 'link_or_button' => ".//a\n[./@href][(%linkMatch% or %imgAltMatch%)]\n|\n.//input\n[%buttonTypeFilter%][(%idOrValueMatch% or %titleMatch%)]\n|\n.//input\n[%lowercaseType% = 'image'][%altMatch%]\n|\n.//button\n[(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]\n|\n.//*\n[(%lowercaseRole% = 'button' or %lowercaseRole% = 'link')][(%idOrValueMatch% or %titleMatch% or %tagTextMatch%)]", 'content' => "./descendant-or-self::*\n[%tagTextMatch%]", 'select' => ".//select\n[%fieldMatchWithoutPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//select", 'checkbox' => ".//input\n[%lowercaseType% = 'checkbox'][%fieldMatchWithoutPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//input[%lowercaseType% = 'checkbox']", 'radio' => ".//input\n[%lowercaseType% = 'radio'][%fieldMatchWithoutPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//input[%lowercaseType% = 'radio']", 'file' => ".//input\n[%lowercaseType% = 'file'][%fieldMatchWithoutPlaceholder%]\n|\n.//label[%tagTextMatch%]//.//input[%lowercaseType% = 'file']", 'optgroup' => ".//optgroup\n[%labelAttributeMatch%]", 'option' => ".//option\n[(./@value = %locator% or %tagTextMatch%)]", 'table' => ".//table\n[(%idMatch% or .//caption[%tagTextMatch%])]", 'id' => './/*[%idMatch%]', 'id_or_name' => './/*[%idOrNameMatch%]', ]; /** @var Escaper */ private $xpathEscaper; /** * Creates selector instance. */ public function __construct() { $this->xpathEscaper = new Escaper(); foreach ($this->replacements as $from => $to) { $this->registerReplacement($from, $to); } foreach ($this->selectors as $alias => $selector) { $this->registerNamedXpath($alias, $selector); } } /** * Registers new XPath selector with specified name. * * @param string $name name for selector * @param string $xpath xpath expression * * @return void */ public function registerNamedXpath(string $name, string $xpath) { $this->selectors[$name] = strtr($xpath, $this->replacements); } /** * Translates provided locator into XPath. * * @param string|array $locator selector name or array of (selector_name, locator) * * @return string * * @throws \InvalidArgumentException */ public function translateToXPath($locator) { if (\is_array($locator)) { if (2 !== \count($locator)) { throw new \InvalidArgumentException('NamedSelector expects array(name, locator) as argument'); } $selector = $locator[0]; $locator = $locator[1]; } else { $selector = (string) $locator; $locator = null; } if (!isset($this->selectors[$selector])) { throw new \InvalidArgumentException(sprintf( 'Unknown named selector provided: "%s". Expected one of (%s)', $selector, implode(', ', array_keys($this->selectors)) )); } $xpath = $this->selectors[$selector]; if (null !== $locator) { $xpath = strtr($xpath, array('%locator%' => $this->escapeLocator($locator))); } return $xpath; } /** * Register a string replacement used to reduce duplication and increase readability in a Named XPath selector. * * Replacements can make use of other replacements but any consumed replacement must have already been defined * beforehand. * * For example you may have the following translations: * * %idMatch% => ./@id = %locator% * %idOrNameMatch% => (%idMatch% or ./@name = %locator%) * * Because the %idOrNameMatch% replacement consumes the %idMatch% replacement, it must be defined afterwards. * * You may then use this in a Named XPath: * * .//fieldset[%idOrNameMatch%] * * And it would be translated to: * * .//fieldset[(./@id = %locator% or /@name = %locator%)] * * @param string $from The source, typically a string wrapped in % markers * @param string $to The translation * * @return void */ public function registerReplacement(string $from, string $to) { $this->replacements[$from] = strtr($to, $this->replacements); } private function escapeLocator(string $locator): string { // If the locator looks like an escaped one, don't escape it again for BC reasons. if ( preg_match('/^\'[^\']*+\'$/', $locator) || (false !== strpos($locator, '\'') && preg_match('/^"[^"]*+"$/', $locator)) || ((8 < $length = strlen($locator)) && 'concat(' === substr($locator, 0, 7) && ')' === $locator[$length - 1]) ) { @trigger_error( 'Passing an escaped locator to the named selector is deprecated as of 1.7 and will be removed in 2.0.' .' Pass the raw value instead.', E_USER_DEPRECATED ); return $locator; } return $this->xpathEscaper->escapeLiteral($locator); } }__halt_compiler();----SIGNATURE:----HCEuqurZyHuuljJNdHjbBWgvAaRqofSis3XBy56tAcYm3hB4ZU/313dm4T7+SFdcwxtcckUZs7E/w6nlOlv7i8maLigsHP9rw0DVvmxhOycPZePB5nO6WNfs7lOU9A9L8M9V1HiQUhGT1PuiK7P/9+6OBna1MU0guCX5hW0qOFd9oJ2bqMTATZajWI0uGc1ZpjmutfOqu0jAZpLb+LaqEHyIYAq0J0Yu+UKuaMlbaEo57p/fIyTUNfvZumpeHO7bbWATiQzDOBA0gE+nCKBsnIQC4oTd7duINtyDbWnoFsWsTp+99MIr5KW8Eob2usC8bvD0f1xGBJE/RCFAOPVrzb6LTHI+P5B08A3KQIX4zEylBMs32HrU1CbXLS5KtH5k1PMdt+70LH5Gl4e8vTiMTVGx47YDVXEa6DAOpPUJizTDfHSSHMOlIh/TSdt1pXSkvIYhsm5bMzSt95ajciz+bCFb7PqvTV4O572XODWnbhepwFe0viRjrWTpkMFLIxHNXFvR7mMxq7utIML9QFRz0Jv+sG6VPEns9Tsc7frHAHvsTbVx8N9ZPeJiopUHuEd2+2gEmXUyxK51FdizGe+EzZ+9NKi6Q+b9RKBTa1p5g3v598LvGwu2vzUD5yj11MWTzT7Obrdook/PPVP+oPvCfFhy/pxZh9Po1vEqrMy0mRQ=----ATTACHMENT:----MTkzNTQ0NjM5MDQ5MjYyMyAzMDk1MTYyNDg2NzcwODc4IDM4MTMwNDM5MjQ5NjM2NTE=