the whole shebang

This commit is contained in:
2014-11-25 16:42:40 +01:00
parent 7f74c0613e
commit ab1334c0cf
3686 changed files with 496409 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -0,0 +1,26 @@
CHANGELOG
=========
2.3.0
-----
* added Crawler::html()
* [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances
* added schema relative URL support to links
* added support for HTML5 'form' attribute
2.2.0
-----
* added a way to set raw path to the file in FileFormField - necessary for
simulating HTTP requests
2.1.0
-----
* added support for the HTTP PATCH method
* refactored the Form class internals to support multi-dimensional fields
(the public API is backward compatible)
* added a way to get parsing errors for Crawler::addHtmlContent() and
Crawler::addXmlContent() via libxml functions
* added support for submitting a form without a submit button

View File

@@ -0,0 +1,777 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler;
use Symfony\Component\CssSelector\CssSelector;
/**
* Crawler eases navigation of a list of \DOMNode objects.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class Crawler extends \SplObjectStorage
{
/**
* @var string The current URI or the base href value
*/
protected $uri;
/**
* Constructor.
*
* @param mixed $node A Node to use as the base for the crawling
* @param string $uri The current URI or the base href value
*
* @api
*/
public function __construct($node = null, $uri = null)
{
$this->uri = $uri;
$this->add($node);
}
/**
* Removes all the nodes.
*
* @api
*/
public function clear()
{
$this->removeAll($this);
}
/**
* Adds a node to the current list of nodes.
*
* This method uses the appropriate specialized add*() method based
* on the type of the argument.
*
* @param \DOMNodeList|\DOMNode|array|string|null $node A node
*
* @throws \InvalidArgumentException When node is not the expected type.
*
* @api
*/
public function add($node)
{
if ($node instanceof \DOMNodeList) {
$this->addNodeList($node);
} elseif ($node instanceof \DOMNode) {
$this->addNode($node);
} elseif (is_array($node)) {
$this->addNodes($node);
} elseif (is_string($node)) {
$this->addContent($node);
} elseif (null !== $node) {
throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node)));
}
}
/**
* Adds HTML/XML content.
*
* If the charset is not set via the content type, it is assumed
* to be ISO-8859-1, which is the default charset defined by the
* HTTP 1.1 specification.
*
* @param string $content A string to parse as HTML/XML
* @param null|string $type The content type of the string
*
* @return null|void
*/
public function addContent($content, $type = null)
{
if (empty($type)) {
$type = 'text/html';
}
// DOM only for HTML/XML content
if (!preg_match('/(x|ht)ml/i', $type, $matches)) {
return null;
}
$charset = 'ISO-8859-1';
if (false !== $pos = strpos($type, 'charset=')) {
$charset = substr($type, $pos + 8);
if (false !== $pos = strpos($charset, ';')) {
$charset = substr($charset, 0, $pos);
}
}
if ('x' === $matches[1]) {
$this->addXmlContent($content, $charset);
} else {
$this->addHtmlContent($content, $charset);
}
}
/**
* Adds an HTML content to the list of nodes.
*
* The libxml errors are disabled when the content is parsed.
*
* If you want to get parsing errors, be sure to enable
* internal errors via libxml_use_internal_errors(true)
* and then, get the errors via libxml_get_errors(). Be
* sure to clear errors with libxml_clear_errors() afterward.
*
* @param string $content The HTML content
* @param string $charset The charset
*
* @api
*/
public function addHtmlContent($content, $charset = 'UTF-8')
{
$current = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
$dom = new \DOMDocument('1.0', $charset);
$dom->validateOnParse = true;
if (function_exists('mb_convert_encoding') && in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
$content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset);
}
@$dom->loadHTML($content);
libxml_use_internal_errors($current);
libxml_disable_entity_loader($disableEntities);
$this->addDocument($dom);
$base = $this->filterXPath('descendant-or-self::base')->extract(array('href'));
$baseHref = current($base);
if (count($base) && !empty($baseHref)) {
if ($this->uri) {
$linkNode = $dom->createElement('a');
$linkNode->setAttribute('href', $baseHref);
$link = new Link($linkNode, $this->uri);
$this->uri = $link->getUri();
} else {
$this->uri = $baseHref;
}
}
}
/**
* Adds an XML content to the list of nodes.
*
* The libxml errors are disabled when the content is parsed.
*
* If you want to get parsing errors, be sure to enable
* internal errors via libxml_use_internal_errors(true)
* and then, get the errors via libxml_get_errors(). Be
* sure to clear errors with libxml_clear_errors() afterward.
*
* @param string $content The XML content
* @param string $charset The charset
*
* @api
*/
public function addXmlContent($content, $charset = 'UTF-8')
{
$current = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
$dom = new \DOMDocument('1.0', $charset);
$dom->validateOnParse = true;
// remove the default namespace to make XPath expressions simpler
@$dom->loadXML(str_replace('xmlns', 'ns', $content), LIBXML_NONET);
libxml_use_internal_errors($current);
libxml_disable_entity_loader($disableEntities);
$this->addDocument($dom);
}
/**
* Adds a \DOMDocument to the list of nodes.
*
* @param \DOMDocument $dom A \DOMDocument instance
*
* @api
*/
public function addDocument(\DOMDocument $dom)
{
if ($dom->documentElement) {
$this->addNode($dom->documentElement);
}
}
/**
* Adds a \DOMNodeList to the list of nodes.
*
* @param \DOMNodeList $nodes A \DOMNodeList instance
*
* @api
*/
public function addNodeList(\DOMNodeList $nodes)
{
foreach ($nodes as $node) {
$this->addNode($node);
}
}
/**
* Adds an array of \DOMNode instances to the list of nodes.
*
* @param \DOMNode[] $nodes An array of \DOMNode instances
*
* @api
*/
public function addNodes(array $nodes)
{
foreach ($nodes as $node) {
$this->add($node);
}
}
/**
* Adds a \DOMNode instance to the list of nodes.
*
* @param \DOMNode $node A \DOMNode instance
*
* @api
*/
public function addNode(\DOMNode $node)
{
if ($node instanceof \DOMDocument) {
$this->attach($node->documentElement);
} else {
$this->attach($node);
}
}
/**
* Returns a node given its position in the node list.
*
* @param integer $position The position
*
* @return Crawler A new instance of the Crawler with the selected node, or an empty Crawler if it does not exist.
*
* @api
*/
public function eq($position)
{
foreach ($this as $i => $node) {
if ($i == $position) {
return new static($node, $this->uri);
}
}
return new static(null, $this->uri);
}
/**
* Calls an anonymous function on each node of the list.
*
* The anonymous function receives the position and the node wrapped
* in a Crawler instance as arguments.
*
* Example:
*
* $crawler->filter('h1')->each(function ($node, $i) {
* return $node->text();
* });
*
* @param \Closure $closure An anonymous function
*
* @return array An array of values returned by the anonymous function
*
* @api
*/
public function each(\Closure $closure)
{
$data = array();
foreach ($this as $i => $node) {
$data[] = $closure(new static($node, $this->uri), $i);
}
return $data;
}
/**
* Reduces the list of nodes by calling an anonymous function.
*
* To remove a node from the list, the anonymous function must return false.
*
* @param \Closure $closure An anonymous function
*
* @return Crawler A Crawler instance with the selected nodes.
*
* @api
*/
public function reduce(\Closure $closure)
{
$nodes = array();
foreach ($this as $i => $node) {
if (false !== $closure(new static($node, $this->uri), $i)) {
$nodes[] = $node;
}
}
return new static($nodes, $this->uri);
}
/**
* Returns the first node of the current selection
*
* @return Crawler A Crawler instance with the first selected node
*
* @api
*/
public function first()
{
return $this->eq(0);
}
/**
* Returns the last node of the current selection
*
* @return Crawler A Crawler instance with the last selected node
*
* @api
*/
public function last()
{
return $this->eq(count($this) - 1);
}
/**
* Returns the siblings nodes of the current selection
*
* @return Crawler A Crawler instance with the sibling nodes
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function siblings()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri);
}
/**
* Returns the next siblings nodes of the current selection
*
* @return Crawler A Crawler instance with the next sibling nodes
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function nextAll()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
return new static($this->sibling($this->getNode(0)), $this->uri);
}
/**
* Returns the previous sibling nodes of the current selection
*
* @return Crawler A Crawler instance with the previous sibling nodes
*
* @throws \InvalidArgumentException
*
* @api
*/
public function previousAll()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri);
}
/**
* Returns the parents nodes of the current selection
*
* @return Crawler A Crawler instance with the parents nodes of the current selection
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function parents()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
$node = $this->getNode(0);
$nodes = array();
while ($node = $node->parentNode) {
if (1 === $node->nodeType && '_root' !== $node->nodeName) {
$nodes[] = $node;
}
}
return new static($nodes, $this->uri);
}
/**
* Returns the children nodes of the current selection
*
* @return Crawler A Crawler instance with the children nodes
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function children()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
$node = $this->getNode(0)->firstChild;
return new static($node ? $this->sibling($node) : array(), $this->uri);
}
/**
* Returns the attribute value of the first node of the list.
*
* @param string $attribute The attribute name
*
* @return string The attribute value
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function attr($attribute)
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
return $this->getNode(0)->getAttribute($attribute);
}
/**
* Returns the node value of the first node of the list.
*
* @return string The node value
*
* @throws \InvalidArgumentException When current node is empty
*
* @api
*/
public function text()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
return $this->getNode(0)->nodeValue;
}
/**
* Returns the first node of the list as HTML.
*
* @return string The node html
*
* @throws \InvalidArgumentException When current node is empty
*/
public function html()
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
$html = '';
foreach ($this->getNode(0)->childNodes as $child) {
if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
// node parameter was added to the saveHTML() method in PHP 5.3.6
// @see http://php.net/manual/en/domdocument.savehtml.php
$html .= $child->ownerDocument->saveHTML($child);
} else {
$document = new \DOMDocument('1.0', 'UTF-8');
$document->appendChild($document->importNode($child, true));
$html .= rtrim($document->saveHTML());
}
}
return $html;
}
/**
* Extracts information from the list of nodes.
*
* You can extract attributes or/and the node value (_text).
*
* Example:
*
* $crawler->filter('h1 a')->extract(array('_text', 'href'));
*
* @param array $attributes An array of attributes
*
* @return array An array of extracted values
*
* @api
*/
public function extract($attributes)
{
$attributes = (array) $attributes;
$data = array();
foreach ($this as $node) {
$elements = array();
foreach ($attributes as $attribute) {
if ('_text' === $attribute) {
$elements[] = $node->nodeValue;
} else {
$elements[] = $node->getAttribute($attribute);
}
}
$data[] = count($attributes) > 1 ? $elements : $elements[0];
}
return $data;
}
/**
* Filters the list of nodes with an XPath expression.
*
* @param string $xpath An XPath expression
*
* @return Crawler A new instance of Crawler with the filtered list of nodes
*
* @api
*/
public function filterXPath($xpath)
{
$document = new \DOMDocument('1.0', 'UTF-8');
$root = $document->appendChild($document->createElement('_root'));
foreach ($this as $node) {
$root->appendChild($document->importNode($node, true));
}
$domxpath = new \DOMXPath($document);
return new static($domxpath->query($xpath), $this->uri);
}
/**
* Filters the list of nodes with a CSS selector.
*
* This method only works if you have installed the CssSelector Symfony Component.
*
* @param string $selector A CSS selector
*
* @return Crawler A new instance of Crawler with the filtered list of nodes
*
* @throws \RuntimeException if the CssSelector Component is not available
*
* @api
*/
public function filter($selector)
{
if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) {
// @codeCoverageIgnoreStart
throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).');
// @codeCoverageIgnoreEnd
}
return $this->filterXPath(CssSelector::toXPath($selector));
}
/**
* Selects links by name or alt value for clickable images.
*
* @param string $value The link text
*
* @return Crawler A new instance of Crawler with the filtered list of nodes
*
* @api
*/
public function selectLink($value)
{
$xpath = sprintf('//a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s)] ', static::xpathLiteral(' '.$value.' ')).
sprintf('| //a/img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]/ancestor::a', static::xpathLiteral(' '.$value.' '));
return $this->filterXPath($xpath);
}
/**
* Selects a button by name or alt value for images.
*
* @param string $value The button text
*
* @return Crawler A new instance of Crawler with the filtered list of nodes
*
* @api
*/
public function selectButton($value)
{
$xpath = sprintf('//input[((@type="submit" or @type="button") and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', static::xpathLiteral(' '.$value.' ')).
sprintf('or (@type="image" and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id="%s" or @name="%s"] ', static::xpathLiteral(' '.$value.' '), $value, $value).
sprintf('| //button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id="%s" or @name="%s"]', static::xpathLiteral(' '.$value.' '), $value, $value);
return $this->filterXPath($xpath);
}
/**
* Returns a Link object for the first node in the list.
*
* @param string $method The method for the link (get by default)
*
* @return Link A Link instance
*
* @throws \InvalidArgumentException If the current node list is empty
*
* @api
*/
public function link($method = 'get')
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
$node = $this->getNode(0);
return new Link($node, $this->uri, $method);
}
/**
* Returns an array of Link objects for the nodes in the list.
*
* @return Link[] An array of Link instances
*
* @api
*/
public function links()
{
$links = array();
foreach ($this as $node) {
$links[] = new Link($node, $this->uri, 'get');
}
return $links;
}
/**
* Returns a Form object for the first node in the list.
*
* @param array $values An array of values for the form fields
* @param string $method The method for the form
*
* @return Form A Form instance
*
* @throws \InvalidArgumentException If the current node list is empty
*
* @api
*/
public function form(array $values = null, $method = null)
{
if (!count($this)) {
throw new \InvalidArgumentException('The current node list is empty.');
}
$form = new Form($this->getNode(0), $this->uri, $method);
if (null !== $values) {
$form->setValues($values);
}
return $form;
}
/**
* Converts string for XPath expressions.
*
* Escaped characters are: quotes (") and apostrophe (').
*
* Examples:
* <code>
* echo Crawler::xpathLiteral('foo " bar');
* //prints 'foo " bar'
*
* echo Crawler::xpathLiteral("foo ' bar");
* //prints "foo ' bar"
*
* echo Crawler::xpathLiteral('a\'b"c');
* //prints concat('a', "'", 'b"c')
* </code>
*
* @param string $s String to be escaped
*
* @return string Converted string
*/
public static function xpathLiteral($s)
{
if (false === strpos($s, "'")) {
return sprintf("'%s'", $s);
}
if (false === strpos($s, '"')) {
return sprintf('"%s"', $s);
}
$string = $s;
$parts = array();
while (true) {
if (false !== $pos = strpos($string, "'")) {
$parts[] = sprintf("'%s'", substr($string, 0, $pos));
$parts[] = "\"'\"";
$string = substr($string, $pos + 1);
} else {
$parts[] = "'$string'";
break;
}
}
return sprintf("concat(%s)", implode($parts, ', '));
}
protected function getNode($position)
{
foreach ($this as $i => $node) {
if ($i == $position) {
return $node;
}
// @codeCoverageIgnoreStart
}
return null;
// @codeCoverageIgnoreEnd
}
protected function sibling($node, $siblingDir = 'nextSibling')
{
$nodes = array();
do {
if ($node !== $this->getNode(0) && $node->nodeType === 1) {
$nodes[] = $node;
}
} while ($node = $node->$siblingDir);
return $nodes;
}
}

View File

@@ -0,0 +1,307 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* ChoiceFormField represents a choice form field.
*
* It is constructed from a HTML select tag, or a HTML checkbox, or radio inputs.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class ChoiceFormField extends FormField
{
/**
* @var string
*/
private $type;
/**
* @var Boolean
*/
private $multiple;
/**
* @var array
*/
private $options;
/**
* Returns true if the field should be included in the submitted values.
*
* @return Boolean true if the field should be included in the submitted values, false otherwise
*/
public function hasValue()
{
// don't send a value for unchecked checkboxes
if (in_array($this->type, array('checkbox', 'radio')) && null === $this->value) {
return false;
}
return true;
}
/**
* Check if the current selected option is disabled
*
* @return Boolean
*/
public function isDisabled()
{
foreach ($this->options as $option) {
if ($option['value'] == $this->value && $option['disabled']) {
return true;
}
}
return false;
}
/**
* Sets the value of the field.
*
* @param string $value The value of the field
*
* @api
*/
public function select($value)
{
$this->setValue($value);
}
/**
* Ticks a checkbox.
*
* @throws \LogicException When the type provided is not correct
*
* @api
*/
public function tick()
{
if ('checkbox' !== $this->type) {
throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
}
$this->setValue(true);
}
/**
* Ticks a checkbox.
*
* @throws \LogicException When the type provided is not correct
*
* @api
*/
public function untick()
{
if ('checkbox' !== $this->type) {
throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
}
$this->setValue(false);
}
/**
* Sets the value of the field.
*
* @param string $value The value of the field
*
* @throws \InvalidArgumentException When value type provided is not correct
*/
public function setValue($value)
{
if ('checkbox' == $this->type && false === $value) {
// uncheck
$this->value = null;
} elseif ('checkbox' == $this->type && true === $value) {
// check
$this->value = $this->options[0]['value'];
} else {
if (is_array($value)) {
if (!$this->multiple) {
throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name));
}
foreach ($value as $v) {
if (!$this->containsOption($v, $this->options)) {
throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues())));
}
}
} elseif (!$this->containsOption($value, $this->options)) {
throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues())));
}
if ($this->multiple) {
$value = (array) $value;
}
if (is_array($value)) {
$this->value = $value;
} else {
parent::setValue($value);
}
}
}
/**
* Adds a choice to the current ones.
*
* This method should only be used internally.
*
* @param \DOMNode $node A \DOMNode
*
* @throws \LogicException When choice provided is not multiple nor radio
*/
public function addChoice(\DOMNode $node)
{
if (!$this->multiple && 'radio' != $this->type) {
throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name));
}
$option = $this->buildOptionValue($node);
$this->options[] = $option;
if ($node->getAttribute('checked')) {
$this->value = $option['value'];
}
}
/**
* Returns the type of the choice field (radio, select, or checkbox).
*
* @return string The type
*/
public function getType()
{
return $this->type;
}
/**
* Returns true if the field accepts multiple values.
*
* @return Boolean true if the field accepts multiple values, false otherwise
*/
public function isMultiple()
{
return $this->multiple;
}
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize()
{
if ('input' != $this->node->nodeName && 'select' != $this->node->nodeName) {
throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName));
}
if ('input' == $this->node->nodeName && 'checkbox' != $this->node->getAttribute('type') && 'radio' != $this->node->getAttribute('type')) {
throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type')));
}
$this->value = null;
$this->options = array();
$this->multiple = false;
if ('input' == $this->node->nodeName) {
$this->type = $this->node->getAttribute('type');
$optionValue = $this->buildOptionValue($this->node);
$this->options[] = $optionValue;
if ($this->node->getAttribute('checked')) {
$this->value = $optionValue['value'];
}
} else {
$this->type = 'select';
if ($this->node->hasAttribute('multiple')) {
$this->multiple = true;
$this->value = array();
$this->name = str_replace('[]', '', $this->name);
}
$found = false;
foreach ($this->xpath->query('descendant::option', $this->node) as $option) {
$this->options[] = $this->buildOptionValue($option);
if ($option->getAttribute('selected')) {
$found = true;
if ($this->multiple) {
$this->value[] = $option->getAttribute('value');
} else {
$this->value = $option->getAttribute('value');
}
}
}
// if no option is selected and if it is a simple select box, take the first option as the value
$option = $this->xpath->query('descendant::option', $this->node)->item(0);
if (!$found && !$this->multiple && $option instanceof \DOMElement) {
$this->value = $option->getAttribute('value');
}
}
}
/**
* Returns option value with associated disabled flag
*
* @param \DOMNode $node
*
* @return array
*/
private function buildOptionValue($node)
{
$option = array();
$defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : '1';
$option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue;
$option['disabled'] = ($node->hasAttribute('disabled') && $node->getAttribute('disabled') == 'disabled');
return $option;
}
/**
* Checks whether given vale is in the existing options
*
* @param string $optionValue
* @param array $options
*
* @return bool
*/
public function containsOption($optionValue, $options)
{
foreach ($options as $option) {
if ($option['value'] == $optionValue) {
return true;
}
}
return false;
}
/**
* Returns list of available field options
*
* @return array
*/
public function availableOptionValues()
{
$values = array();
foreach ($this->options as $option) {
$values[] = $option['value'];
}
return $values;
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* FileFormField represents a file form field (an HTML file input tag).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class FileFormField extends FormField
{
/**
* Sets the PHP error code associated with the field.
*
* @param integer $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION)
*
* @throws \InvalidArgumentException When error code doesn't exist
*/
public function setErrorCode($error)
{
$codes = array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION);
if (!in_array($error, $codes)) {
throw new \InvalidArgumentException(sprintf('The error code %s is not valid.', $error));
}
$this->value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0);
}
/**
* Sets the value of the field.
*
* @param string $value The value of the field
*
* @api
*/
public function upload($value)
{
$this->setValue($value);
}
/**
* Sets the value of the field.
*
* @param string $value The value of the field
*/
public function setValue($value)
{
if (null !== $value && is_readable($value)) {
$error = UPLOAD_ERR_OK;
$size = filesize($value);
$info = pathinfo($value);
$name = $info['basename'];
// copy to a tmp location
$tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true));
if (array_key_exists('extension', $info)) {
$tmp .= '.'.$info['extension'];
}
if (is_file($tmp)) {
unlink($tmp);
}
copy($value, $tmp);
$value = $tmp;
} else {
$error = UPLOAD_ERR_NO_FILE;
$size = 0;
$name = '';
$value = '';
}
$this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size);
}
/**
* Sets path to the file as string for simulating HTTP request
*
* @param string $path The path to the file
*/
public function setFilePath($path)
{
parent::setValue($path);
}
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize()
{
if ('input' != $this->node->nodeName) {
throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName));
}
if ('file' != $this->node->getAttribute('type')) {
throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type')));
}
$this->setValue(null);
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* FormField is the abstract class for all form fields.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FormField
{
/**
* @var \DOMNode
*/
protected $node;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $value;
/**
* @var \DOMDocument
*/
protected $document;
/**
* @var \DOMXPath
*/
protected $xpath;
/**
* @var Boolean
*/
protected $disabled;
/**
* Constructor.
*
* @param \DOMNode $node The node associated with this field
*/
public function __construct(\DOMNode $node)
{
$this->node = $node;
$this->name = $node->getAttribute('name');
$this->document = new \DOMDocument('1.0', 'UTF-8');
$this->node = $this->document->importNode($this->node, true);
$root = $this->document->appendChild($this->document->createElement('_root'));
$root->appendChild($this->node);
$this->xpath = new \DOMXPath($this->document);
$this->initialize();
}
/**
* Returns the name of the field.
*
* @return string The name of the field
*/
public function getName()
{
return $this->name;
}
/**
* Gets the value of the field.
*
* @return string|array The value of the field
*/
public function getValue()
{
return $this->value;
}
/**
* Sets the value of the field.
*
* @param string $value The value of the field
*
* @api
*/
public function setValue($value)
{
$this->value = (string) $value;
}
/**
* Returns true if the field should be included in the submitted values.
*
* @return Boolean true if the field should be included in the submitted values, false otherwise
*/
public function hasValue()
{
return true;
}
/**
* Check if the current field is disabled
*
* @return Boolean
*/
public function isDisabled()
{
return $this->node->hasAttribute('disabled');
}
/**
* Initializes the form field.
*/
abstract protected function initialize();
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* InputFormField represents an input form field (an HTML input tag).
*
* For inputs with type of file, checkbox, or radio, there are other more
* specialized classes (cf. FileFormField and ChoiceFormField).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class InputFormField extends FormField
{
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize()
{
if ('input' != $this->node->nodeName && 'button' != $this->node->nodeName) {
throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName));
}
if ('checkbox' == $this->node->getAttribute('type')) {
throw new \LogicException('Checkboxes should be instances of ChoiceFormField.');
}
if ('file' == $this->node->getAttribute('type')) {
throw new \LogicException('File inputs should be instances of FileFormField.');
}
$this->value = $this->node->getAttribute('value');
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Field;
/**
* TextareaFormField represents a textarea form field (an HTML textarea tag).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class TextareaFormField extends FormField
{
/**
* Initializes the form field.
*
* @throws \LogicException When node type is incorrect
*/
protected function initialize()
{
if ('textarea' != $this->node->nodeName) {
throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName));
}
$this->value = null;
foreach ($this->node->childNodes as $node) {
$this->value .= $this->document->saveXML($node);
}
}
}

View File

@@ -0,0 +1,425 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler;
use Symfony\Component\DomCrawler\Field\FormField;
/**
* Form represents an HTML form.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class Form extends Link implements \ArrayAccess
{
/**
* @var \DOMNode
*/
private $button;
/**
* @var Field\FormField[]
*/
private $fields;
/**
* Constructor.
*
* @param \DOMNode $node A \DOMNode instance
* @param string $currentUri The URI of the page where the form is embedded
* @param string $method The method to use for the link (if null, it defaults to the method defined by the form)
*
* @throws \LogicException if the node is not a button inside a form tag
*
* @api
*/
public function __construct(\DOMNode $node, $currentUri, $method = null)
{
parent::__construct($node, $currentUri, $method);
$this->initialize();
}
/**
* Gets the form node associated with this form.
*
* @return \DOMNode A \DOMNode instance
*/
public function getFormNode()
{
return $this->node;
}
/**
* Sets the value of the fields.
*
* @param array $values An array of field values
*
* @return Form
*
* @api
*/
public function setValues(array $values)
{
foreach ($values as $name => $value) {
$this->fields->set($name, $value);
}
return $this;
}
/**
* Gets the field values.
*
* The returned array does not include file fields (@see getFiles).
*
* @return array An array of field values.
*
* @api
*/
public function getValues()
{
$values = array();
foreach ($this->fields->all() as $name => $field) {
if ($field->isDisabled()) {
continue;
}
if (!$field instanceof Field\FileFormField && $field->hasValue()) {
$values[$name] = $field->getValue();
}
}
return $values;
}
/**
* Gets the file field values.
*
* @return array An array of file field values.
*
* @api
*/
public function getFiles()
{
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) {
return array();
}
$files = array();
foreach ($this->fields->all() as $name => $field) {
if ($field->isDisabled()) {
continue;
}
if ($field instanceof Field\FileFormField) {
$files[$name] = $field->getValue();
}
}
return $files;
}
/**
* Gets the field values as PHP.
*
* This method converts fields with the array notation
* (like foo[bar] to arrays) like PHP does.
*
* @return array An array of field values.
*
* @api
*/
public function getPhpValues()
{
$qs = http_build_query($this->getValues(), '', '&');
parse_str($qs, $values);
return $values;
}
/**
* Gets the file field values as PHP.
*
* This method converts fields with the array notation
* (like foo[bar] to arrays) like PHP does.
*
* @return array An array of field values.
*
* @api
*/
public function getPhpFiles()
{
$qs = http_build_query($this->getFiles(), '', '&');
parse_str($qs, $values);
return $values;
}
/**
* Gets the URI of the form.
*
* The returned URI is not the same as the form "action" attribute.
* This method merges the value if the method is GET to mimics
* browser behavior.
*
* @return string The URI
*
* @api
*/
public function getUri()
{
$uri = parent::getUri();
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH')) && $queryString = http_build_query($this->getValues(), null, '&')) {
$sep = false === strpos($uri, '?') ? '?' : '&';
$uri .= $sep.$queryString;
}
return $uri;
}
protected function getRawUri()
{
return $this->node->getAttribute('action');
}
/**
* Gets the form method.
*
* If no method is defined in the form, GET is returned.
*
* @return string The method
*
* @api
*/
public function getMethod()
{
if (null !== $this->method) {
return $this->method;
}
return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET';
}
/**
* Returns true if the named field exists.
*
* @param string $name The field name
*
* @return Boolean true if the field exists, false otherwise
*
* @api
*/
public function has($name)
{
return $this->fields->has($name);
}
/**
* Removes a field from the form.
*
* @param string $name The field name
*
* @throws \InvalidArgumentException when the name is malformed
*
* @api
*/
public function remove($name)
{
$this->fields->remove($name);
}
/**
* Gets a named field.
*
* @param string $name The field name
*
* @return FormField The field instance
*
* @throws \InvalidArgumentException When field is not present in this form
*
* @api
*/
public function get($name)
{
return $this->fields->get($name);
}
/**
* Sets a named field.
*
* @param FormField $field The field
*
* @api
*/
public function set(FormField $field)
{
$this->fields->add($field);
}
/**
* Gets all fields.
*
* @return FormField[] An array of fields
*
* @api
*/
public function all()
{
return $this->fields->all();
}
/**
* Returns true if the named field exists.
*
* @param string $name The field name
*
* @return Boolean true if the field exists, false otherwise
*/
public function offsetExists($name)
{
return $this->has($name);
}
/**
* Gets the value of a field.
*
* @param string $name The field name
*
* @return FormField The associated Field instance
*
* @throws \InvalidArgumentException if the field does not exist
*/
public function offsetGet($name)
{
return $this->fields->get($name);
}
/**
* Sets the value of a field.
*
* @param string $name The field name
* @param string|array $value The value of the field
*
* @throws \InvalidArgumentException if the field does not exist
*/
public function offsetSet($name, $value)
{
$this->fields->set($name, $value);
}
/**
* Removes a field from the form.
*
* @param string $name The field name
*/
public function offsetUnset($name)
{
$this->fields->remove($name);
}
/**
* Sets the node for the form.
*
* Expects a 'submit' button \DOMNode and finds the corresponding form element.
*
* @param \DOMNode $node A \DOMNode instance
*
* @throws \LogicException If given node is not a button or input or does not have a form ancestor
*/
protected function setNode(\DOMNode $node)
{
$this->button = $node;
if ('button' == $node->nodeName || ('input' == $node->nodeName && in_array($node->getAttribute('type'), array('submit', 'button', 'image')))) {
if ($node->hasAttribute('form')) {
// if the node has the HTML5-compliant 'form' attribute, use it
$formId = $node->getAttribute('form');
$form = $node->ownerDocument->getElementById($formId);
if (null === $form) {
throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId));
}
$this->node = $form;
return;
}
// we loop until we find a form ancestor
do {
if (null === $node = $node->parentNode) {
throw new \LogicException('The selected node does not have a form ancestor.');
}
} while ('form' != $node->nodeName);
} elseif ('form' != $node->nodeName) {
throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName));
}
$this->node = $node;
}
private function initialize()
{
$this->fields = new FormFieldRegistry();
$document = new \DOMDocument('1.0', 'UTF-8');
$node = $document->importNode($this->node, true);
$button = $document->importNode($this->button, true);
$root = $document->appendChild($document->createElement('_root'));
$root->appendChild($node);
$root->appendChild($button);
$xpath = new \DOMXPath($document);
// add descendant elements to the form
$fieldNodes = $xpath->query('descendant::input | descendant::button | descendant::textarea | descendant::select', $root);
foreach ($fieldNodes as $node) {
$this->addField($node, $button);
}
// find form elements corresponding to the current form by the HTML5 form attribute
if ($this->node->hasAttribute('id')) {
$formId = Crawler::xpathLiteral($this->node->getAttribute('id'));
$xpath = new \DOMXPath($this->node->ownerDocument);
$fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s]', $formId, $formId, $formId, $formId));
foreach ($fieldNodes as $node) {
$this->addField($node, $button);
}
}
}
private function addField(\DOMNode $node, \DOMNode $button)
{
if (!$node->hasAttribute('name') || !$node->getAttribute('name')) {
return;
}
$nodeName = $node->nodeName;
if ($node === $button) {
$this->set(new Field\InputFormField($node));
} elseif ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == $node->getAttribute('type')) {
$this->set(new Field\ChoiceFormField($node));
} elseif ('input' == $nodeName && 'radio' == $node->getAttribute('type')) {
if ($this->has($node->getAttribute('name'))) {
$this->get($node->getAttribute('name'))->addChoice($node);
} else {
$this->set(new Field\ChoiceFormField($node));
}
} elseif ('input' == $nodeName && 'file' == $node->getAttribute('type')) {
$this->set(new Field\FileFormField($node));
} elseif ('input' == $nodeName && !in_array($node->getAttribute('type'), array('submit', 'button', 'image'))) {
$this->set(new Field\InputFormField($node));
} elseif ('textarea' == $nodeName) {
$this->set(new Field\TextareaFormField($node));
}
}
}

View File

@@ -0,0 +1,220 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler;
use Symfony\Component\DomCrawler\Field\FormField;
/**
* This is an internal class that must not be used directly.
*/
class FormFieldRegistry
{
private $fields = array();
private $base;
/**
* Adds a field to the registry.
*
* @param FormField $field The field
*
* @throws \InvalidArgumentException when the name is malformed
*/
public function add(FormField $field)
{
$segments = $this->getSegments($field->getName());
$target =& $this->fields;
while ($segments) {
if (!is_array($target)) {
$target = array();
}
$path = array_shift($segments);
if ('' === $path) {
$target =& $target[];
} else {
$target =& $target[$path];
}
}
$target = $field;
}
/**
* Removes a field and its children from the registry.
*
* @param string $name The fully qualified name of the base field
*
* @throws \InvalidArgumentException when the name is malformed
*/
public function remove($name)
{
$segments = $this->getSegments($name);
$target =& $this->fields;
while (count($segments) > 1) {
$path = array_shift($segments);
if (!array_key_exists($path, $target)) {
return;
}
$target =& $target[$path];
}
unset($target[array_shift($segments)]);
}
/**
* Returns the value of the field and its children.
*
* @param string $name The fully qualified name of the field
*
* @return mixed The value of the field
*
* @throws \InvalidArgumentException when the name is malformed
* @throws \InvalidArgumentException if the field does not exist
*/
public function &get($name)
{
$segments = $this->getSegments($name);
$target =& $this->fields;
while ($segments) {
$path = array_shift($segments);
if (!array_key_exists($path, $target)) {
throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path));
}
$target =& $target[$path];
}
return $target;
}
/**
* Tests whether the form has the given field.
*
* @param string $name The fully qualified name of the field
*
* @return Boolean Whether the form has the given field
*/
public function has($name)
{
try {
$this->get($name);
return true;
} catch (\InvalidArgumentException $e) {
return false;
}
}
/**
* Set the value of a field and its children.
*
* @param string $name The fully qualified name of the field
* @param mixed $value The value
*
* @throws \InvalidArgumentException when the name is malformed
* @throws \InvalidArgumentException if the field does not exist
*/
public function set($name, $value)
{
$target =& $this->get($name);
if (!is_array($value) || $target instanceof Field\ChoiceFormField) {
$target->setValue($value);
} else {
$fields = self::create($name, $value);
foreach ($fields->all() as $k => $v) {
$this->set($k, $v);
}
}
}
/**
* Returns the list of field with their value.
*
* @return array The list of fields as array((string) Fully qualified name => (mixed) value)
*/
public function all()
{
return $this->walk($this->fields, $this->base);
}
/**
* Creates an instance of the class.
*
* This function is made private because it allows overriding the $base and
* the $values properties without any type checking.
*
* @param string $base The fully qualified name of the base field
* @param array $values The values of the fields
*
* @return FormFieldRegistry
*/
private static function create($base, array $values)
{
$registry = new static();
$registry->base = $base;
$registry->fields = $values;
return $registry;
}
/**
* Transforms a PHP array in a list of fully qualified name / value.
*
* @param array $array The PHP array
* @param string $base The name of the base field
* @param array $output The initial values
*
* @return array The list of fields as array((string) Fully qualified name => (mixed) value)
*/
private function walk(array $array, $base = '', array &$output = array())
{
foreach ($array as $k => $v) {
$path = empty($base) ? $k : sprintf("%s[%s]", $base, $k);
if (is_array($v)) {
$this->walk($v, $path, $output);
} else {
$output[$path] = $v;
}
}
return $output;
}
/**
* Splits a field name into segments as a web browser would do.
*
* <code>
* getSegments('base[foo][3][]') = array('base', 'foo, '3', '');
* </code>
*
* @param string $name The name of the field
*
* @return array The list of segments
*
* @throws \InvalidArgumentException when the name is malformed
*/
private function getSegments($name)
{
if (preg_match('/^(?P<base>[^[]+)(?P<extra>(\[.*)|$)/', $name, $m)) {
$segments = array($m['base']);
while (!empty($m['extra'])) {
if (preg_match('/^\[(?P<segment>.*?)\](?P<extra>.*)$/', $m['extra'], $m)) {
$segments[] = $m['segment'];
} else {
throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name));
}
}
return $segments;
}
throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name));
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-2013 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler;
/**
* Link represents an HTML link (an HTML a tag).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class Link
{
/**
* @var \DOMNode A \DOMNode instance
*/
protected $node;
/**
* @var string The method to use for the link
*/
protected $method;
/**
* @var string The URI of the page where the link is embedded (or the base href)
*/
protected $currentUri;
/**
* Constructor.
*
* @param \DOMNode $node A \DOMNode instance
* @param string $currentUri The URI of the page where the link is embedded (or the base href)
* @param string $method The method to use for the link (get by default)
*
* @throws \InvalidArgumentException if the node is not a link
*
* @api
*/
public function __construct(\DOMNode $node, $currentUri, $method = 'GET')
{
if (!in_array(strtolower(substr($currentUri, 0, 4)), array('http', 'file'))) {
throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri));
}
$this->setNode($node);
$this->method = $method ? strtoupper($method) : null;
$this->currentUri = $currentUri;
}
/**
* Gets the node associated with this link.
*
* @return \DOMNode A \DOMNode instance
*/
public function getNode()
{
return $this->node;
}
/**
* Gets the method associated with this link.
*
* @return string The method
*
* @api
*/
public function getMethod()
{
return $this->method;
}
/**
* Gets the URI associated with this link.
*
* @return string The URI
*
* @api
*/
public function getUri()
{
$uri = trim($this->getRawUri());
// absolute URL?
if (null !== parse_url($uri, PHP_URL_SCHEME)) {
return $uri;
}
// empty URI
if (!$uri) {
return $this->currentUri;
}
// only an anchor
if ('#' === $uri[0]) {
$baseUri = $this->currentUri;
if (false !== $pos = strpos($baseUri, '#')) {
$baseUri = substr($baseUri, 0, $pos);
}
return $baseUri.$uri;
}
// only a query string
if ('?' === $uri[0]) {
$baseUri = $this->currentUri;
// remove the query string from the current uri
if (false !== $pos = strpos($baseUri, '?')) {
$baseUri = substr($baseUri, 0, $pos);
}
return $baseUri.$uri;
}
// absolute URL with relative schema
if (0 === strpos($uri, '//')) {
return preg_replace('#^([^/]*)//.*$#', '$1', $this->currentUri).$uri;
}
$baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $this->currentUri);
// absolute path
if ('/' === $uri[0]) {
return $baseUri.$uri;
}
// relative path
$path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH);
$path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri);
return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path;
}
/**
* Returns raw uri data.
*
* @return string
*/
protected function getRawUri()
{
return $this->node->getAttribute('href');
}
/**
* Returns the canonicalized URI path (see RFC 3986, section 5.2.4)
*
* @param string $path URI path
*
* @return string
*/
protected function canonicalizePath($path)
{
if ('' === $path || '/' === $path) {
return $path;
}
if ('.' === substr($path, -1)) {
$path = $path.'/';
}
$output = array();
foreach (explode('/', $path) as $segment) {
if ('..' === $segment) {
array_pop($output);
} elseif ('.' !== $segment) {
array_push($output, $segment);
}
}
return implode('/', $output);
}
/**
* Sets current \DOMNode instance.
*
* @param \DOMNode $node A \DOMNode instance
*
* @throws \LogicException If given node is not an anchor
*/
protected function setNode(\DOMNode $node)
{
if ('a' != $node->nodeName) {
throw new \LogicException(sprintf('Unable to click on a "%s" tag.', $node->nodeName));
}
$this->node = $node;
}
}

View File

@@ -0,0 +1,32 @@
DomCrawler Component
====================
DomCrawler eases DOM navigation for HTML and XML documents.
If you are familiar with jQuery, DomCrawler is a PHP equivalent:
use Symfony\Component\DomCrawler\Crawler;
$crawler = new Crawler();
$crawler->addContent('<html><body><p>Hello World!</p></body></html>');
print $crawler->filterXPath('descendant-or-self::body/p')->text();
If you are also using the CssSelector component, you can use CSS Selectors
instead of XPath expressions:
use Symfony\Component\DomCrawler\Crawler;
$crawler = new Crawler();
$crawler->addContent('<html><body><p>Hello World!</p></body></html>');
print $crawler->filter('body > p')->text();
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/DomCrawler/
$ composer.phar install --dev
$ phpunit

View File

@@ -0,0 +1,675 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests;
use Symfony\Component\DomCrawler\Crawler;
class CrawlerTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$crawler = new Crawler();
$this->assertCount(0, $crawler, '__construct() returns an empty crawler');
$crawler = new Crawler(new \DOMNode());
$this->assertCount(1, $crawler, '__construct() takes a node as a first argument');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::add
*/
public function testAdd()
{
$crawler = new Crawler();
$crawler->add($this->createDomDocument());
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMDocument');
$crawler = new Crawler();
$crawler->add($this->createNodeList());
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNodeList');
foreach ($this->createNodeList() as $node) {
$list[] = $node;
}
$crawler = new Crawler();
$crawler->add($list);
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an array of nodes');
$crawler = new Crawler();
$crawler->add($this->createNodeList()->item(0));
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an \DOMNode');
$crawler = new Crawler();
$crawler->add('<html><body>Foo</body></html>');
$this->assertEquals('Foo', $crawler->filterXPath('//body')->text(), '->add() adds nodes from a string');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testAddInvalidNode()
{
$crawler = new Crawler();
$crawler->add(1);
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
*/
public function testAddHtmlContent()
{
$crawler = new Crawler();
$crawler->addHtmlContent('<html><div class="foo"></html>', 'UTF-8');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addHtmlContent() adds nodes from an HTML string');
$crawler->addHtmlContent('<html><head><base href="http://symfony.com"></head><a href="/contact"></a></html>', 'UTF-8');
$this->assertEquals('http://symfony.com', $crawler->filterXPath('//base')->attr('href'), '->addHtmlContent() adds nodes from an HTML string');
$this->assertEquals('http://symfony.com/contact', $crawler->filterXPath('//a')->link()->getUri(), '->addHtmlContent() adds nodes from an HTML string');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
*/
public function testAddHtmlContentCharset()
{
$crawler = new Crawler();
$crawler->addHtmlContent('<html><div class="foo">Tiếng Việt</html>', 'UTF-8');
$this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text());
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
*/
public function testAddHtmlContentInvalidBaseTag()
{
$crawler = new Crawler(null, 'http://symfony.com');
$crawler->addHtmlContent('<html><head><base target="_top"></head><a href="/contact"></a></html>', 'UTF-8');
$this->assertEquals('http://symfony.com/contact', current($crawler->filterXPath('//a')->links())->getUri(), '->addHtmlContent() correctly handles a non-existent base tag href attribute');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
*/
public function testAddHtmlContentUnsupportedCharset()
{
$crawler = new Crawler();
$crawler->addHtmlContent(file_get_contents(__DIR__.'/Fixtures/windows-1250.html'), 'Windows-1250');
$this->assertEquals('Žťčýů', $crawler->filterXPath('//p')->text());
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent
*/
public function testAddHtmlContentWithErrors()
{
libxml_use_internal_errors(true);
$crawler = new Crawler();
$crawler->addHtmlContent(<<<EOF
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<nav><a href="#"><a href="#"></nav>
</body>
</html>
EOF
, 'UTF-8');
$errors = libxml_get_errors();
$this->assertCount(1, $errors);
$this->assertEquals("Tag nav invalid\n", $errors[0]->message);
libxml_clear_errors();
libxml_use_internal_errors(false);
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addXmlContent
*/
public function testAddXmlContent()
{
$crawler = new Crawler();
$crawler->addXmlContent('<html><div class="foo"></div></html>', 'UTF-8');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addXmlContent() adds nodes from an XML string');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addXmlContent
*/
public function testAddXmlContentCharset()
{
$crawler = new Crawler();
$crawler->addXmlContent('<html><div class="foo">Tiếng Việt</div></html>', 'UTF-8');
$this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text());
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addXmlContent
*/
public function testAddXmlContentWithErrors()
{
libxml_use_internal_errors(true);
$crawler = new Crawler();
$crawler->addXmlContent(<<<EOF
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<nav><a href="#"><a href="#"></nav>
</body>
</html>
EOF
, 'UTF-8');
$this->assertTrue(count(libxml_get_errors()) > 1);
libxml_clear_errors();
libxml_use_internal_errors(false);
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addContent
*/
public function testAddContent()
{
$crawler = new Crawler();
$crawler->addContent('<html><div class="foo"></html>', 'text/html; charset=UTF-8');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string');
$crawler = new Crawler();
$crawler->addContent('<html><div class="foo"></html>', 'text/html; charset=UTF-8; dir=RTL');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string with extended content type');
$crawler = new Crawler();
$crawler->addContent('<html><div class="foo"></html>');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() uses text/html as the default type');
$crawler = new Crawler();
$crawler->addContent('<html><div class="foo"></div></html>', 'text/xml; charset=UTF-8');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string');
$crawler = new Crawler();
$crawler->addContent('<html><div class="foo"></div></html>', 'text/xml');
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string');
$crawler = new Crawler();
$crawler->addContent('foo bar', 'text/plain');
$this->assertCount(0, $crawler, '->addContent() does nothing if the type is not (x|ht)ml');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addDocument
*/
public function testAddDocument()
{
$crawler = new Crawler();
$crawler->addDocument($this->createDomDocument());
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addDocument() adds nodes from a \DOMDocument');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addNodeList
*/
public function testAddNodeList()
{
$crawler = new Crawler();
$crawler->addNodeList($this->createNodeList());
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodeList() adds nodes from a \DOMNodeList');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addNodes
*/
public function testAddNodes()
{
foreach ($this->createNodeList() as $node) {
$list[] = $node;
}
$crawler = new Crawler();
$crawler->addNodes($list);
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodes() adds nodes from an array of nodes');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::addNode
*/
public function testAddNode()
{
$crawler = new Crawler();
$crawler->addNode($this->createNodeList()->item(0));
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNode() adds nodes from an \DOMNode');
}
public function testClear()
{
$crawler = new Crawler(new \DOMNode());
$crawler->clear();
$this->assertCount(0, $crawler, '->clear() removes all the nodes from the crawler');
}
public function testEq()
{
$crawler = $this->createTestCrawler()->filterXPath('//li');
$this->assertNotSame($crawler, $crawler->eq(0), '->eq() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->eq() returns a new instance of a crawler');
$this->assertEquals('Two', $crawler->eq(1)->text(), '->eq() returns the nth node of the list');
$this->assertCount(0, $crawler->eq(100), '->eq() returns an empty crawler if the nth node does not exist');
}
public function testEach()
{
$data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) {
return $i.'-'.$node->text();
});
$this->assertEquals(array('0-One', '1-Two', '2-Three'), $data, '->each() executes an anonymous function on each node of the list');
}
public function testReduce()
{
$crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li');
$nodes = $crawler->reduce(function ($node, $i) {
return $i == 1 ? false : true;
});
$this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $nodes, '->reduce() returns a new instance of a crawler');
$this->assertCount(2, $nodes, '->reduce() filters the nodes in the list');
}
public function testAttr()
{
$this->assertEquals('first', $this->createTestCrawler()->filterXPath('//li')->attr('class'), '->attr() returns the attribute of the first element of the node list');
try {
$this->createTestCrawler()->filterXPath('//ol')->attr('class');
$this->fail('->attr() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->attr() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testText()
{
$this->assertEquals('One', $this->createTestCrawler()->filterXPath('//li')->text(), '->text() returns the node value of the first element of the node list');
try {
$this->createTestCrawler()->filterXPath('//ol')->text();
$this->fail('->text() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->text() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testHtml()
{
$this->assertEquals('<img alt="Bar">', $this->createTestCrawler()->filterXPath('//a[5]')->html());
$this->assertEquals('<input type="text" value="TextValue" name="TextName"><input type="submit" value="FooValue" name="FooName" id="FooId"><input type="button" value="BarValue" name="BarName" id="BarId"><button value="ButtonValue" name="ButtonName" id="ButtonId"></button>'
, trim($this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html()));
try {
$this->createTestCrawler()->filterXPath('//ol')->html();
$this->fail('->html() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->html() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testExtract()
{
$crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li');
$this->assertEquals(array('One', 'Two', 'Three'), $crawler->extract('_text'), '->extract() returns an array of extracted data from the node list');
$this->assertEquals(array(array('One', 'first'), array('Two', ''), array('Three', '')), $crawler->extract(array('_text', 'class')), '->extract() returns an array of extracted data from the node list');
$this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->extract('_text'), '->extract() returns an empty array if the node list is empty');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::filterXPath
*/
public function testFilterXPath()
{
$crawler = $this->createTestCrawler();
$this->assertNotSame($crawler, $crawler->filterXPath('//li'), '->filterXPath() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler');
$crawler = $this->createTestCrawler()->filterXPath('//ul');
$this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression');
}
/**
* @covers Symfony\Component\DomCrawler\Crawler::filter
*/
public function testFilter()
{
if (!class_exists('Symfony\Component\CssSelector\CssSelector')) {
$this->markTestSkipped('The "CssSelector" component is not available');
}
$crawler = $this->createTestCrawler();
$this->assertNotSame($crawler, $crawler->filter('li'), '->filter() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filter() returns a new instance of a crawler');
$crawler = $this->createTestCrawler()->filter('ul');
$this->assertCount(6, $crawler->filter('li'), '->filter() filters the node list with the CSS selector');
}
public function testSelectLink()
{
$crawler = $this->createTestCrawler();
$this->assertNotSame($crawler, $crawler->selectLink('Foo'), '->selectLink() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectLink() returns a new instance of a crawler');
$this->assertCount(1, $crawler->selectLink('Fabien\'s Foo'), '->selectLink() selects links by the node values');
$this->assertCount(1, $crawler->selectLink('Fabien\'s Bar'), '->selectLink() selects links by the alt attribute of a clickable image');
$this->assertCount(2, $crawler->selectLink('Fabien"s Foo'), '->selectLink() selects links by the node values');
$this->assertCount(2, $crawler->selectLink('Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image');
$this->assertCount(1, $crawler->selectLink('\' Fabien"s Foo'), '->selectLink() selects links by the node values');
$this->assertCount(1, $crawler->selectLink('\' Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image');
$this->assertCount(4, $crawler->selectLink('Foo'), '->selectLink() selects links by the node values');
$this->assertCount(4, $crawler->selectLink('Bar'), '->selectLink() selects links by the node values');
}
public function testSelectButton()
{
$crawler = $this->createTestCrawler();
$this->assertNotSame($crawler, $crawler->selectButton('FooValue'), '->selectButton() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectButton() returns a new instance of a crawler');
$this->assertEquals(1, $crawler->selectButton('FooValue')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('FooName')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('FooId')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('BarValue')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('BarName')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('BarId')->count(), '->selectButton() selects buttons');
$this->assertEquals(1, $crawler->selectButton('FooBarValue')->count(), '->selectButton() selects buttons with form attribute too');
$this->assertEquals(1, $crawler->selectButton('FooBarName')->count(), '->selectButton() selects buttons with form attribute too');
}
public function testLink()
{
$crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $crawler->link(), '->link() returns a Link instance');
$this->assertEquals('POST', $crawler->link('post')->getMethod(), '->link() takes a method as its argument');
$crawler = $this->createTestCrawler('http://example.com/bar')->selectLink('GetLink');
$this->assertEquals('http://example.com/bar?get=param', $crawler->link()->getUri(), '->link() returns a Link instance');
try {
$this->createTestCrawler()->filterXPath('//ol')->link();
$this->fail('->link() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->link() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testLinks()
{
$crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo');
$this->assertInternalType('array', $crawler->links(), '->links() returns an array');
$this->assertCount(4, $crawler->links(), '->links() returns an array');
$links = $crawler->links();
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $links[0], '->links() returns an array of Link instances');
$this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->links(), '->links() returns an empty array if the node selection is empty');
}
public function testForm()
{
$testCrawler = $this->createTestCrawler('http://example.com/bar/');
$crawler = $testCrawler->selectButton('FooValue');
$crawler2 = $testCrawler->selectButton('FooBarValue');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler->form(), '->form() returns a Form instance');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler2->form(), '->form() returns a Form instance');
$this->assertEquals($crawler->form()->getFormNode()->getAttribute('id'), $crawler2->form()->getFormNode()->getAttribute('id'), '->form() works on elements with form attribute');
$this->assertEquals(array('FooName' => 'FooBar', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument');
$this->assertEquals(array('FooName' => 'FooValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form()->getValues(), '->form() takes an array of values to submit as its first argument');
$this->assertEquals(array('FooBarName' => 'FooBarValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler2->form()->getValues(), '->form() takes an array of values to submit as its first argument');
try {
$this->createTestCrawler()->filterXPath('//ol')->form();
$this->fail('->form() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->form() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testLast()
{
$crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li');
$this->assertNotSame($crawler, $crawler->last(), '->last() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->last() returns a new instance of a crawler');
$this->assertEquals('Three', $crawler->last()->text());
}
public function testFirst()
{
$crawler = $this->createTestCrawler()->filterXPath('//li');
$this->assertNotSame($crawler, $crawler->first(), '->first() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->first() returns a new instance of a crawler');
$this->assertEquals('One', $crawler->first()->text());
}
public function testSiblings()
{
$crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1);
$this->assertNotSame($crawler, $crawler->siblings(), '->siblings() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->siblings() returns a new instance of a crawler');
$nodes = $crawler->siblings();
$this->assertEquals(2, $nodes->count());
$this->assertEquals('One', $nodes->eq(0)->text());
$this->assertEquals('Three', $nodes->eq(1)->text());
$nodes = $this->createTestCrawler()->filterXPath('//li')->eq(0)->siblings();
$this->assertEquals(2, $nodes->count());
$this->assertEquals('Two', $nodes->eq(0)->text());
$this->assertEquals('Three', $nodes->eq(1)->text());
try {
$this->createTestCrawler()->filterXPath('//ol')->siblings();
$this->fail('->siblings() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->siblings() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testNextAll()
{
$crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1);
$this->assertNotSame($crawler, $crawler->nextAll(), '->nextAll() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->nextAll() returns a new instance of a crawler');
$nodes = $crawler->nextAll();
$this->assertEquals(1, $nodes->count());
$this->assertEquals('Three', $nodes->eq(0)->text());
try {
$this->createTestCrawler()->filterXPath('//ol')->nextAll();
$this->fail('->nextAll() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->nextAll() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testPreviousAll()
{
$crawler = $this->createTestCrawler()->filterXPath('//li')->eq(2);
$this->assertNotSame($crawler, $crawler->previousAll(), '->previousAll() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->previousAll() returns a new instance of a crawler');
$nodes = $crawler->previousAll();
$this->assertEquals(2, $nodes->count());
$this->assertEquals('Two', $nodes->eq(0)->text());
try {
$this->createTestCrawler()->filterXPath('//ol')->previousAll();
$this->fail('->previousAll() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->previousAll() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testChildren()
{
$crawler = $this->createTestCrawler()->filterXPath('//ul');
$this->assertNotSame($crawler, $crawler->children(), '->children() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->children() returns a new instance of a crawler');
$nodes = $crawler->children();
$this->assertEquals(3, $nodes->count());
$this->assertEquals('One', $nodes->eq(0)->text());
$this->assertEquals('Two', $nodes->eq(1)->text());
$this->assertEquals('Three', $nodes->eq(2)->text());
try {
$this->createTestCrawler()->filterXPath('//ol')->children();
$this->fail('->children() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->children() throws an \InvalidArgumentException if the node list is empty');
}
try {
$crawler = new Crawler('<p></p>');
$crawler->filter('p')->children();
$this->assertTrue(true, '->children() does not trigger a notice if the node has no children');
} catch (\PHPUnit_Framework_Error_Notice $e) {
$this->fail('->children() does not trigger a notice if the node has no children');
}
}
public function testParents()
{
$crawler = $this->createTestCrawler()->filterXPath('//li[1]');
$this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler');
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler');
$nodes = $crawler->parents();
$this->assertEquals(3, $nodes->count());
$nodes = $this->createTestCrawler()->filterXPath('//html')->parents();
$this->assertEquals(0, $nodes->count());
try {
$this->createTestCrawler()->filterXPath('//ol')->parents();
$this->fail('->parents() throws an \InvalidArgumentException if the node list is empty');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->parents() throws an \InvalidArgumentException if the node list is empty');
}
}
public function testBaseTag()
{
$crawler = new Crawler('<html><base href="http://base.com"><a href="link"></a></html>');
$this->assertEquals('http://base.com/link', $crawler->filterXPath('//a')->link()->getUri());
$crawler = new Crawler('<html><base href="//base.com"><a href="link"></a></html>', 'https://domain.com');
$this->assertEquals('https://base.com/link', $crawler->filterXPath('//a')->link()->getUri(), '<base> tag can use a schema-less url');
$crawler = new Crawler('<html><base href="path/"><a href="link"></a></html>', 'https://domain.com');
$this->assertEquals('https://domain.com/path/link', $crawler->filterXPath('//a')->link()->getUri(), '<base> tag can set a path');
}
public function createTestCrawler($uri = null)
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<body>
<a href="foo">Foo</a>
<a href="/foo"> Fabien\'s Foo </a>
<a href="/foo">Fabien"s Foo</a>
<a href="/foo">\' Fabien"s Foo</a>
<a href="/bar"><img alt="Bar"/></a>
<a href="/bar"><img alt=" Fabien\'s Bar "/></a>
<a href="/bar"><img alt="Fabien&quot;s Bar"/></a>
<a href="/bar"><img alt="\' Fabien&quot;s Bar"/></a>
<a href="?get=param">GetLink</a>
<form action="foo" id="FooFormId">
<input type="text" value="TextValue" name="TextName" />
<input type="submit" value="FooValue" name="FooName" id="FooId" />
<input type="button" value="BarValue" name="BarName" id="BarId" />
<button value="ButtonValue" name="ButtonName" id="ButtonId" />
</form>
<input type="submit" value="FooBarValue" name="FooBarName" form="FooFormId" />
<input type="text" value="FooTextValue" name="FooTextName" form="FooFormId" />
<ul class="first">
<li class="first">One</li>
<li>Two</li>
<li>Three</li>
</ul>
<ul>
<li>One Bis</li>
<li>Two Bis</li>
<li>Three Bis</li>
</ul>
</body>
</html>
');
return new Crawler($dom, $uri);
}
protected function createDomDocument()
{
$dom = new \DOMDocument();
$dom->loadXML('<html><div class="foo"></div></html>');
return $dom;
}
protected function createNodeList()
{
$dom = new \DOMDocument();
$dom->loadXML('<html><div class="foo"></div></html>');
$domxpath = new \DOMXPath($dom);
return $domxpath->query('//div');
}
}

View File

@@ -0,0 +1,329 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
use Symfony\Component\DomCrawler\Field\ChoiceFormField;
class ChoiceFormFieldTest extends FormFieldTestCase
{
public function testInitialize()
{
$node = $this->createNode('textarea', '');
try {
$field = new ChoiceFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input or a select');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input or a select');
}
$node = $this->createNode('input', '', array('type' => 'text'));
try {
$field = new ChoiceFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is an input with a type different from checkbox or radio');
}
}
public function testGetType()
{
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$this->assertEquals('radio', $field->getType(), '->getType() returns radio for radio buttons');
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$this->assertEquals('checkbox', $field->getType(), '->getType() returns radio for a checkbox');
$node = $this->createNode('select', '');
$field = new ChoiceFormField($node);
$this->assertEquals('select', $field->getType(), '->getType() returns radio for a select');
}
public function testIsMultiple()
{
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$this->assertFalse($field->isMultiple(), '->isMultiple() returns false for radio buttons');
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$this->assertFalse($field->isMultiple(), '->isMultiple() returns false for checkboxes');
$node = $this->createNode('select', '');
$field = new ChoiceFormField($node);
$this->assertFalse($field->isMultiple(), '->isMultiple() returns false for selects without the multiple attribute');
$node = $this->createNode('select', '', array('multiple' => 'multiple'));
$field = new ChoiceFormField($node);
$this->assertTrue($field->isMultiple(), '->isMultiple() returns true for selects with the multiple attribute');
}
public function testSelects()
{
$node = $this->createSelectNode(array('foo' => false, 'bar' => false));
$field = new ChoiceFormField($node);
$this->assertTrue($field->hasValue(), '->hasValue() returns true for selects');
$this->assertEquals('foo', $field->getValue(), '->getValue() returns the first option if none are selected');
$this->assertFalse($field->isMultiple(), '->isMultiple() returns false when no multiple attribute is defined');
$node = $this->createSelectNode(array('foo' => false, 'bar' => true));
$field = new ChoiceFormField($node);
$this->assertEquals('bar', $field->getValue(), '->getValue() returns the selected option');
$field->setValue('foo');
$this->assertEquals('foo', $field->getValue(), '->setValue() changes the selected option');
try {
$field->setValue('foobar');
$this->fail('->setValue() throws an \InvalidArgumentException if the value is not one of the selected options');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setValue() throws an \InvalidArgumentException if the value is not one of the selected options');
}
try {
$field->setValue(array('foobar'));
$this->fail('->setValue() throws an \InvalidArgumentException if the value is an array');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setValue() throws an \InvalidArgumentException if the value is an array');
}
}
public function testMultipleSelects()
{
$node = $this->createSelectNode(array('foo' => false, 'bar' => false), array('multiple' => 'multiple'));
$field = new ChoiceFormField($node);
$this->assertEquals(array(), $field->getValue(), '->setValue() returns an empty array if multiple is true and no option is selected');
$field->setValue('foo');
$this->assertEquals(array('foo'), $field->getValue(), '->setValue() returns an array of options if multiple is true');
$field->setValue('bar');
$this->assertEquals(array('bar'), $field->getValue(), '->setValue() returns an array of options if multiple is true');
$field->setValue(array('foo', 'bar'));
$this->assertEquals(array('foo', 'bar'), $field->getValue(), '->setValue() returns an array of options if multiple is true');
$node = $this->createSelectNode(array('foo' => true, 'bar' => true), array('multiple' => 'multiple'));
$field = new ChoiceFormField($node);
$this->assertEquals(array('foo', 'bar'), $field->getValue(), '->getValue() returns the selected options');
try {
$field->setValue(array('foobar'));
$this->fail('->setValue() throws an \InvalidArgumentException if the value is not one of the options');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setValue() throws an \InvalidArgumentException if the value is not one of the options');
}
}
public function testRadioButtons()
{
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'bar'));
$field->addChoice($node);
$this->assertFalse($field->hasValue(), '->hasValue() returns false when no radio button is selected');
$this->assertNull($field->getValue(), '->getValue() returns null if no radio button is selected');
$this->assertFalse($field->isMultiple(), '->isMultiple() returns false for radio buttons');
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'bar', 'checked' => 'checked'));
$field->addChoice($node);
$this->assertTrue($field->hasValue(), '->hasValue() returns true when a radio button is selected');
$this->assertEquals('bar', $field->getValue(), '->getValue() returns the value attribute of the selected radio button');
$field->setValue('foo');
$this->assertEquals('foo', $field->getValue(), '->setValue() changes the selected radio button');
try {
$field->setValue('foobar');
$this->fail('->setValue() throws an \InvalidArgumentException if the value is not one of the radio button values');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setValue() throws an \InvalidArgumentException if the value is not one of the radio button values');
}
}
public function testRadioButtonIsDisabled()
{
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'foo', 'disabled' => 'disabled'));
$field = new ChoiceFormField($node);
$node = $this->createNode('input', '', array('type' => 'radio', 'name' => 'name', 'value' => 'bar'));
$field->addChoice($node);
$field->select('foo');
$this->assertEquals('foo', $field->getValue(), '->getValue() returns the value attribute of the selected radio button');
$this->assertTrue($field->isDisabled());
$field->select('bar');
$this->assertEquals('bar', $field->getValue(), '->getValue() returns the value attribute of the selected radio button');
$this->assertFalse($field->isDisabled());
}
public function testCheckboxes()
{
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name'));
$field = new ChoiceFormField($node);
$this->assertFalse($field->hasValue(), '->hasValue() returns false when the checkbox is not checked');
$this->assertNull($field->getValue(), '->getValue() returns null if the checkbox is not checked');
$this->assertFalse($field->isMultiple(), '->hasValue() returns false for checkboxes');
try {
$field->addChoice(new \DOMNode());
$this->fail('->addChoice() throws a \LogicException for checkboxes');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException for checkboxes');
}
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked'));
$field = new ChoiceFormField($node);
$this->assertTrue($field->hasValue(), '->hasValue() returns true when the checkbox is checked');
$this->assertEquals('1', $field->getValue(), '->getValue() returns 1 if the checkbox is checked and has no value attribute');
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$this->assertEquals('foo', $field->getValue(), '->getValue() returns the value attribute if the checkbox is checked');
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked', 'value' => 'foo'));
$field = new ChoiceFormField($node);
$field->setValue(false);
$this->assertNull($field->getValue(), '->setValue() unchecks the checkbox is value is false');
$field->setValue(true);
$this->assertEquals('foo', $field->getValue(), '->setValue() checks the checkbox is value is true');
try {
$field->setValue('bar');
$this->fail('->setValue() throws an \InvalidArgumentException if the value is not one from the value attribute');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setValue() throws an \InvalidArgumentException if the value is not one from the value attribute');
}
}
public function testTick()
{
$node = $this->createSelectNode(array('foo' => false, 'bar' => false));
$field = new ChoiceFormField($node);
try {
$field->tick();
$this->fail('->tick() throws a \LogicException for select boxes');
} catch (\LogicException $e) {
$this->assertTrue(true, '->tick() throws a \LogicException for select boxes');
}
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name'));
$field = new ChoiceFormField($node);
$field->tick();
$this->assertEquals(1, $field->getValue(), '->tick() ticks checkboxes');
}
public function testUntick()
{
$node = $this->createSelectNode(array('foo' => false, 'bar' => false));
$field = new ChoiceFormField($node);
try {
$field->untick();
$this->fail('->untick() throws a \LogicException for select boxes');
} catch (\LogicException $e) {
$this->assertTrue(true, '->untick() throws a \LogicException for select boxes');
}
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked'));
$field = new ChoiceFormField($node);
$field->untick();
$this->assertNull($field->getValue(), '->untick() unticks checkboxes');
}
public function testSelect()
{
$node = $this->createNode('input', '', array('type' => 'checkbox', 'name' => 'name', 'checked' => 'checked'));
$field = new ChoiceFormField($node);
$field->select(true);
$this->assertEquals(1, $field->getValue(), '->select() changes the value of the field');
$field->select(false);
$this->assertNull($field->getValue(), '->select() changes the value of the field');
$node = $this->createSelectNode(array('foo' => false, 'bar' => false));
$field = new ChoiceFormField($node);
$field->select('foo');
$this->assertEquals('foo', $field->getValue(), '->select() changes the selected option');
}
public function testOptionWithNoValue()
{
$node = $this->createSelectNodeWithEmptyOption(array('foo' => false, 'bar' => false));
$field = new ChoiceFormField($node);
$field->select('foo');
$this->assertEquals('foo', $field->getValue(), '->select() changes the selected option');
}
protected function createSelectNode($options, $attributes = array())
{
$document = new \DOMDocument();
$node = $document->createElement('select');
foreach ($attributes as $name => $value) {
$node->setAttribute($name, $value);
}
$node->setAttribute('name', 'name');
foreach ($options as $value => $selected) {
$option = $document->createElement('option', $value);
$option->setAttribute('value', $value);
if ($selected) {
$option->setAttribute('selected', 'selected');
}
$node->appendChild($option);
}
return $node;
}
protected function createSelectNodeWithEmptyOption($options, $attributes = array())
{
$document = new \DOMDocument();
$node = $document->createElement('select');
foreach ($attributes as $name => $value) {
$node->setAttribute($name, $value);
}
$node->setAttribute('name', 'name');
foreach ($options as $value => $selected) {
$option = $document->createElement('option', $value);
if ($selected) {
$option->setAttribute('selected', 'selected');
}
$node->appendChild($option);
}
return $node;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
use Symfony\Component\DomCrawler\Field\FileFormField;
class FileFormFieldTest extends FormFieldTestCase
{
public function testInitialize()
{
$node = $this->createNode('input', '', array('type' => 'file'));
$field = new FileFormField($node);
$this->assertEquals(array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), $field->getValue(), '->initialize() sets the value of the field to no file uploaded');
$node = $this->createNode('textarea', '');
try {
$field = new FileFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input field');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input field');
}
$node = $this->createNode('input', '', array('type' => 'text'));
try {
$field = new FileFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not a file input field');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a file input field');
}
}
/**
* @dataProvider getSetValueMethods
*/
public function testSetValue($method)
{
$node = $this->createNode('input', '', array('type' => 'file'));
$field = new FileFormField($node);
$field->$method(null);
$this->assertEquals(array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), $field->getValue(), "->$method() clears the uploaded file if the value is null");
$field->$method(__FILE__);
$value = $field->getValue();
$this->assertEquals(basename(__FILE__), $value['name'], "->$method() sets the name of the file field");
$this->assertEquals('', $value['type'], "->$method() sets the type of the file field");
$this->assertInternalType('string', $value['tmp_name'], "->$method() sets the tmp_name of the file field");
$this->assertFileExists($value['tmp_name'], "->$method() creates a copy of the file at the tmp_name path");
$this->assertEquals(0, $value['error'], "->$method() sets the error of the file field");
$this->assertEquals(filesize(__FILE__), $value['size'], "->$method() sets the size of the file field");
$origInfo = pathinfo(__FILE__);
$tmpInfo = pathinfo($value['tmp_name']);
$this->assertEquals(
$origInfo['extension'],
$tmpInfo['extension'],
"->$method() keeps the same file extension in the tmp_name copy"
);
$field->$method(__DIR__.'/../Fixtures/no-extension');
$value = $field->getValue();
$this->assertArrayNotHasKey(
'extension',
pathinfo($value['tmp_name']),
"->$method() does not add a file extension in the tmp_name copy"
);
}
public function getSetValueMethods()
{
return array(
array('setValue'),
array('upload'),
);
}
public function testSetErrorCode()
{
$node = $this->createNode('input', '', array('type' => 'file'));
$field = new FileFormField($node);
$field->setErrorCode(UPLOAD_ERR_FORM_SIZE);
$value = $field->getValue();
$this->assertEquals(UPLOAD_ERR_FORM_SIZE, $value['error'], '->setErrorCode() sets the file input field error code');
try {
$field->setErrorCode('foobar');
$this->fail('->setErrorCode() throws a \InvalidArgumentException if the error code is not valid');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->setErrorCode() throws a \InvalidArgumentException if the error code is not valid');
}
}
public function testSetRawFilePath()
{
$node = $this->createNode('input', '', array('type' => 'file'));
$field = new FileFormField($node);
$field->setFilePath(__FILE__);
$this->assertEquals(__FILE__, $field->getValue());
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
use Symfony\Component\DomCrawler\Field\InputFormField;
class FormFieldTest extends FormFieldTestCase
{
public function testGetName()
{
$node = $this->createNode('input', '', array('type' => 'text', 'name' => 'name', 'value' => 'value'));
$field = new InputFormField($node);
$this->assertEquals('name', $field->getName(), '->getName() returns the name of the field');
}
public function testGetSetHasValue()
{
$node = $this->createNode('input', '', array('type' => 'text', 'name' => 'name', 'value' => 'value'));
$field = new InputFormField($node);
$this->assertEquals('value', $field->getValue(), '->getValue() returns the value of the field');
$field->setValue('foo');
$this->assertEquals('foo', $field->getValue(), '->setValue() sets the value of the field');
$this->assertTrue($field->hasValue(), '->hasValue() always returns true');
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
class FormFieldTestCase extends \PHPUnit_Framework_TestCase
{
protected function createNode($tag, $value, $attributes = array())
{
$document = new \DOMDocument();
$node = $document->createElement($tag, $value);
foreach ($attributes as $name => $value) {
$node->setAttribute($name, $value);
}
return $node;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
use Symfony\Component\DomCrawler\Field\InputFormField;
class InputFormFieldTest extends FormFieldTestCase
{
public function testInitialize()
{
$node = $this->createNode('input', '', array('type' => 'text', 'name' => 'name', 'value' => 'value'));
$field = new InputFormField($node);
$this->assertEquals('value', $field->getValue(), '->initialize() sets the value of the field to the value attribute value');
$node = $this->createNode('textarea', '');
try {
$field = new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not an input');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not an input');
}
$node = $this->createNode('input', '', array('type' => 'checkbox'));
try {
$field = new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is a checkbox');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is a checkbox');
}
$node = $this->createNode('input', '', array('type' => 'file'));
try {
$field = new InputFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is a file');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is a file');
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests\Field;
use Symfony\Component\DomCrawler\Field\TextareaFormField;
class TextareaFormFieldTest extends FormFieldTestCase
{
public function testInitialize()
{
$node = $this->createNode('textarea', 'foo bar');
$field = new TextareaFormField($node);
$this->assertEquals('foo bar', $field->getValue(), '->initialize() sets the value of the field to the textarea node value');
$node = $this->createNode('input', '');
try {
$field = new TextareaFormField($node);
$this->fail('->initialize() throws a \LogicException if the node is not a textarea');
} catch (\LogicException $e) {
$this->assertTrue(true, '->initialize() throws a \LogicException if the node is not a textarea');
}
}
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=windows-1250">
</head>
<body>
<p><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></p>
</body>
</html>

View File

@@ -0,0 +1,748 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests;
use Symfony\Component\DomCrawler\Form;
use Symfony\Component\DomCrawler\FormFieldRegistry;
use Symfony\Component\DomCrawler\Field;
class FormTest extends \PHPUnit_Framework_TestCase
{
public static function setUpBeforeClass()
{
// Ensure that the private helper class FormFieldRegistry is loaded
class_exists('Symfony\\Component\\DomCrawler\\Form');
}
public function testConstructorThrowsExceptionIfTheNodeHasNoFormAncestor()
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<input type="submit" />
<form>
<input type="foo" />
</form>
<button />
</html>
');
$nodes = $dom->getElementsByTagName('input');
try {
$form = new Form($nodes->item(0), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the node has no form ancestor');
} catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor');
}
try {
$form = new Form($nodes->item(1), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the input type is not submit, button, or image');
} catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the input type is not submit, button, or image');
}
$nodes = $dom->getElementsByTagName('button');
try {
$form = new Form($nodes->item(0), 'http://example.com');
$this->fail('__construct() throws a \\LogicException if the node has no form ancestor');
} catch (\LogicException $e) {
$this->assertTrue(true, '__construct() throws a \\LogicException if the node has no form ancestor');
}
}
/**
* __construct() should throw \\LogicException if the form attribute is invalid
* @expectedException \LogicException
*/
public function testConstructorThrowsExceptionIfNoRelatedForm()
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<form id="bar">
<input type="submit" form="nonexistent" />
</form>
<input type="text" form="nonexistent" />
<button />
</html>
');
$nodes = $dom->getElementsByTagName('input');
$form = new Form($nodes->item(0), 'http://example.com');
$form = new Form($nodes->item(1), 'http://example.com');
}
public function testConstructorHandlesFormAttribute()
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<form id="bar">
<input type="submit" form="bar" />
</form>
<input type="submit" form="bar" />
<button />
</html>
');
$nodes = $dom->getElementsByTagName('input');
$form = new Form($nodes->item(0), 'http://example.com');
$this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), 'HTML5-compliant form attribute handled incorrectly');
$form = new Form($nodes->item(1), 'http://example.com');
$this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), 'HTML5-compliant form attribute handled incorrectly');
}
public function testMultiValuedFields()
{
$form = $this->createForm('<form>
<input type="text" name="foo[4]" value="foo" disabled="disabled" />
<input type="text" name="foo" value="foo" disabled="disabled" />
<input type="text" name="foo[2]" value="foo" disabled="disabled" />
<input type="text" name="foo[]" value="foo" disabled="disabled" />
<input type="text" name="bar[foo][]" value="foo" disabled="disabled" />
<input type="text" name="bar[foo][foobar]" value="foo" disabled="disabled" />
<input type="submit" />
</form>
');
$this->assertEquals(
array_keys($form->all()),
array('foo[2]', 'foo[3]', 'bar[foo][0]', 'bar[foo][foobar]')
);
$this->assertEquals($form->get('foo[2]')->getValue(), 'foo');
$this->assertEquals($form->get('foo[3]')->getValue(), 'foo');
$this->assertEquals($form->get('bar[foo][0]')->getValue(), 'foo');
$this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foo');
$form['foo[2]'] = 'bar';
$form['foo[3]'] = 'bar';
$this->assertEquals($form->get('foo[2]')->getValue(), 'bar');
$this->assertEquals($form->get('foo[3]')->getValue(), 'bar');
$form['bar'] = array('foo' => array('0' => 'bar', 'foobar' => 'foobar'));
$this->assertEquals($form->get('bar[foo][0]')->getValue(), 'bar');
$this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foobar');
}
/**
* @dataProvider provideInitializeValues
*/
public function testConstructor($message, $form, $values)
{
$form = $this->createForm('<form>'.$form.'</form>');
$this->assertEquals(
$values,
array_map(function ($field) {
$class = get_class($field);
return array(substr($class, strrpos($class, '\\') + 1), $field->getValue());
},
$form->all()
),
'->getDefaultValues() '.$message
);
}
public function provideInitializeValues()
{
return array(
array(
'does not take into account input fields without a name attribute',
'<input type="text" value="foo" />
<input type="submit" />',
array(),
),
array(
'does not take into account input fields with an empty name attribute value',
'<input type="text" name="" value="foo" />
<input type="submit" />',
array(),
),
array(
'takes into account disabled input fields',
'<input type="text" name="foo" value="foo" disabled="disabled" />
<input type="submit" />',
array('foo' => array('InputFormField', 'foo')),
),
array(
'appends the submitted button value',
'<input type="submit" name="bar" value="bar" />',
array('bar' => array('InputFormField', 'bar')),
),
array(
'appends the submitted button value for Button element',
'<button type="submit" name="bar" value="bar">Bar</button>',
array('bar' => array('InputFormField', 'bar')),
),
array(
'appends the submitted button value but not other submit buttons',
'<input type="submit" name="bar" value="bar" />
<input type="submit" name="foobar" value="foobar" />',
array('foobar' => array('InputFormField', 'foobar')),
),
array(
'returns textareas',
'<textarea name="foo">foo</textarea>
<input type="submit" />',
array('foo' => array('TextareaFormField', 'foo')),
),
array(
'returns inputs',
'<input type="text" name="foo" value="foo" />
<input type="submit" />',
array('foo' => array('InputFormField', 'foo')),
),
array(
'returns checkboxes',
'<input type="checkbox" name="foo" value="foo" checked="checked" />
<input type="submit" />',
array('foo' => array('ChoiceFormField', 'foo')),
),
array(
'returns not-checked checkboxes',
'<input type="checkbox" name="foo" value="foo" />
<input type="submit" />',
array('foo' => array('ChoiceFormField', false)),
),
array(
'returns radio buttons',
'<input type="radio" name="foo" value="foo" />
<input type="radio" name="foo" value="bar" checked="bar" />
<input type="submit" />',
array('foo' => array('ChoiceFormField', 'bar')),
),
array(
'returns file inputs',
'<input type="file" name="foo" />
<input type="submit" />',
array('foo' => array('FileFormField', array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))),
),
);
}
public function testGetFormNode()
{
$dom = new \DOMDocument();
$dom->loadHTML('<html><form><input type="submit" /></form></html>');
$form = new Form($dom->getElementsByTagName('input')->item(0), 'http://example.com');
$this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form');
}
public function testGetMethod()
{
$form = $this->createForm('<form><input type="submit" /></form>');
$this->assertEquals('GET', $form->getMethod(), '->getMethod() returns get if no method is defined');
$form = $this->createForm('<form method="post"><input type="submit" /></form>');
$this->assertEquals('POST', $form->getMethod(), '->getMethod() returns the method attribute value of the form');
$form = $this->createForm('<form method="post"><input type="submit" /></form>', 'put');
$this->assertEquals('PUT', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided');
$form = $this->createForm('<form method="post"><input type="submit" /></form>', 'delete');
$this->assertEquals('DELETE', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided');
$form = $this->createForm('<form method="post"><input type="submit" /></form>', 'patch');
$this->assertEquals('PATCH', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided');
}
public function testGetSetValue()
{
$form = $this->createForm('<form><input type="text" name="foo" value="foo" /><input type="submit" /></form>');
$this->assertEquals('foo', $form['foo']->getValue(), '->offsetGet() returns the value of a form field');
$form['foo'] = 'bar';
$this->assertEquals('bar', $form['foo']->getValue(), '->offsetSet() changes the value of a form field');
try {
$form['foobar'] = 'bar';
$this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist');
}
try {
$form['foobar'];
$this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist');
}
}
public function testSetValueOnMultiValuedFieldsWithMalformedName()
{
$form = $this->createForm('<form><input type="text" name="foo[bar]" value="bar" /><input type="text" name="foo[baz]" value="baz" /><input type="submit" /></form>');
try {
$form['foo[bar'] = 'bar';
$this->fail('->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.');
}
}
public function testOffsetUnset()
{
$form = $this->createForm('<form><input type="text" name="foo" value="foo" /><input type="submit" /></form>');
unset($form['foo']);
$this->assertFalse(isset($form['foo']), '->offsetUnset() removes a field');
}
public function testOffsetExists()
{
$form = $this->createForm('<form><input type="text" name="foo" value="foo" /><input type="submit" /></form>');
$this->assertTrue(isset($form['foo']), '->offsetExists() return true if the field exists');
$this->assertFalse(isset($form['bar']), '->offsetExists() return false if the field does not exist');
}
public function testGetValues()
{
$form = $this->createForm('<form><input type="text" name="foo[bar]" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo[bar]' => 'foo', 'bar' => 'bar'), $form->getValues(), '->getValues() returns all form field values');
$form = $this->createForm('<form><input type="checkbox" name="foo" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include not-checked checkboxes');
$form = $this->createForm('<form><input type="file" name="foo" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include file input fields');
$form = $this->createForm('<form><input type="text" name="foo" value="foo" disabled="disabled" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields');
}
public function testSetValues()
{
$form = $this->createForm('<form><input type="checkbox" name="foo" value="foo" checked="checked" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$form->setValues(array('foo' => false, 'bar' => 'foo'));
$this->assertEquals(array('bar' => 'foo'), $form->getValues(), '->setValues() sets the values of fields');
}
public function testMultiselectSetValues()
{
$form = $this->createForm('<form><select multiple="multiple" name="multi"><option value="foo">foo</option><option value="bar">bar</option></select><input type="submit" /></form>');
$form->setValues(array('multi' => array("foo", "bar")));
$this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select');
}
public function testGetPhpValues()
{
$form = $this->createForm('<form><input type="text" name="foo[bar]" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays');
}
public function testGetFiles()
{
$form = $this->createForm('<form><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array(), $form->getFiles(), '->getFiles() returns an empty array if method is get');
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for POST');
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>', 'put');
$this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PUT');
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>', 'delete');
$this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for DELETE');
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>', 'patch');
$this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PATCH');
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" disabled="disabled" /><input type="submit" /></form>');
$this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields');
}
public function testGetPhpFiles()
{
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays');
}
/**
* @dataProvider provideGetUriValues
*/
public function testGetUri($message, $form, $values, $uri, $method = null)
{
$form = $this->createForm($form, $method);
$form->setValues($values);
$this->assertEquals('http://example.com'.$uri, $form->getUri(), '->getUri() '.$message);
}
public function testGetBaseUri()
{
$dom = new \DOMDocument();
$dom->loadHTML('<form method="post" action="foo.php"><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$nodes = $dom->getElementsByTagName('input');
$form = new Form($nodes->item($nodes->length - 1), 'http://www.foo.com/');
$this->assertEquals('http://www.foo.com/foo.php', $form->getUri());
}
public function testGetUriWithAnchor()
{
$form = $this->createForm('<form action="#foo"><input type="submit" /></form>', null, 'http://example.com/id/123');
$this->assertEquals('http://example.com/id/123#foo', $form->getUri());
}
public function testGetUriActionAbsolute()
{
$formHtml='<form id="login_form" action="https://login.foo.com/login.php?login_attempt=1" method="POST"><input type="text" name="foo" value="foo" /><input type="submit" /></form>';
$form = $this->createForm($formHtml);
$this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form');
$form = $this->createForm($formHtml, null, 'https://login.foo.com');
$this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form');
$form = $this->createForm($formHtml, null, 'https://login.foo.com/bar/');
$this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form');
// The action URI haven't the same domain Host have an another domain as Host
$form = $this->createForm($formHtml, null, 'https://www.foo.com');
$this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form');
$form = $this->createForm($formHtml, null, 'https://www.foo.com/bar/');
$this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form');
}
public function testGetUriAbsolute()
{
$form = $this->createForm('<form action="foo"><input type="submit" /></form>', null, 'http://localhost/foo/');
$this->assertEquals('http://localhost/foo/foo', $form->getUri(), '->getUri() returns absolute URIs');
$form = $this->createForm('<form action="/foo"><input type="submit" /></form>', null, 'http://localhost/foo/');
$this->assertEquals('http://localhost/foo', $form->getUri(), '->getUri() returns absolute URIs');
}
public function testGetUriWithOnlyQueryString()
{
$form = $this->createForm('<form action="?get=param"><input type="submit" /></form>', null, 'http://localhost/foo/bar');
$this->assertEquals('http://localhost/foo/bar?get=param', $form->getUri(), '->getUri() returns absolute URIs only if the host has been defined in the constructor');
}
public function testGetUriWithoutAction()
{
$form = $this->createForm('<form><input type="submit" /></form>', null, 'http://localhost/foo/bar');
$this->assertEquals('http://localhost/foo/bar', $form->getUri(), '->getUri() returns path if no action defined');
}
public function provideGetUriValues()
{
return array(
array(
'returns the URI of the form',
'<form action="/foo"><input type="submit" /></form>',
array(),
'/foo'
),
array(
'appends the form values if the method is get',
'<form action="/foo"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo?foo=foo'
),
array(
'appends the form values and merges the submitted values',
'<form action="/foo"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array('foo' => 'bar'),
'/foo?foo=bar'
),
array(
'does not append values if the method is post',
'<form action="/foo" method="post"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo'
),
array(
'does not append values if the method is patch',
'<form action="/foo" method="post"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo',
'PUT'
),
array(
'does not append values if the method is delete',
'<form action="/foo" method="post"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo',
'DELETE'
),
array(
'does not append values if the method is put',
'<form action="/foo" method="post"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo',
'PATCH'
),
array(
'appends the form values to an existing query string',
'<form action="/foo?bar=bar"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/foo?bar=bar&foo=foo'
),
array(
'returns an empty URI if the action is empty',
'<form><input type="submit" /></form>',
array(),
'/',
),
array(
'appends the form values even if the action is empty',
'<form><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/?foo=foo',
),
array(
'chooses the path if the action attribute value is a sharp (#)',
'<form action="#" method="post"><input type="text" name="foo" value="foo" /><input type="submit" /></form>',
array(),
'/#',
),
);
}
public function testHas()
{
$form = $this->createForm('<form method="post"><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertFalse($form->has('foo'), '->has() returns false if a field is not in the form');
$this->assertTrue($form->has('bar'), '->has() returns true if a field is in the form');
}
public function testRemove()
{
$form = $this->createForm('<form method="post"><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$form->remove('bar');
$this->assertFalse($form->has('bar'), '->remove() removes a field');
}
public function testGet()
{
$form = $this->createForm('<form method="post"><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals('Symfony\\Component\\DomCrawler\\Field\\InputFormField', get_class($form->get('bar')), '->get() returns the field object associated with the given name');
try {
$form->get('foo');
$this->fail('->get() throws an \InvalidArgumentException if the field does not exist');
} catch (\InvalidArgumentException $e) {
$this->assertTrue(true, '->get() throws an \InvalidArgumentException if the field does not exist');
}
}
public function testAll()
{
$form = $this->createForm('<form method="post"><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$fields = $form->all();
$this->assertEquals(1, count($fields), '->all() return an array of form field objects');
$this->assertEquals('Symfony\\Component\\DomCrawler\\Field\\InputFormField', get_class($fields['bar']), '->all() return an array of form field objects');
}
public function testSubmitWithoutAFormButton()
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<form>
<input type="foo" />
</form>
</html>
');
$nodes = $dom->getElementsByTagName('form');
$form = new Form($nodes->item(0), 'http://example.com');
$this->assertSame($nodes->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistryAddThrowAnExceptionWhenTheNameIsMalformed()
{
$registry = new FormFieldRegistry();
$registry->add($this->getFormFieldMock('[foo]'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistryRemoveThrowAnExceptionWhenTheNameIsMalformed()
{
$registry = new FormFieldRegistry();
$registry->remove('[foo]');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistryGetThrowAnExceptionWhenTheNameIsMalformed()
{
$registry = new FormFieldRegistry();
$registry->get('[foo]');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistryGetThrowAnExceptionWhenTheFieldDoesNotExist()
{
$registry = new FormFieldRegistry();
$registry->get('foo');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistrySetThrowAnExceptionWhenTheNameIsMalformed()
{
$registry = new FormFieldRegistry();
$registry->set('[foo]', null);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFormFieldRegistrySetThrowAnExceptionWhenTheFieldDoesNotExist()
{
$registry = new FormFieldRegistry();
$registry->set('foo', null);
}
public function testFormFieldRegistryHasReturnsTrueWhenTheFQNExists()
{
$registry = new FormFieldRegistry();
$registry->add($this->getFormFieldMock('foo[bar]'));
$this->assertTrue($registry->has('foo'));
$this->assertTrue($registry->has('foo[bar]'));
$this->assertFalse($registry->has('bar'));
$this->assertFalse($registry->has('foo[foo]'));
}
public function testFormRegistryFieldsCanBeRemoved()
{
$registry = new FormFieldRegistry();
$registry->add($this->getFormFieldMock('foo'));
$registry->remove('foo');
$this->assertFalse($registry->has('foo'));
}
public function testFormRegistrySupportsMultivaluedFields()
{
$registry = new FormFieldRegistry();
$registry->add($this->getFormFieldMock('foo[]'));
$registry->add($this->getFormFieldMock('foo[]'));
$registry->add($this->getFormFieldMock('bar[5]'));
$registry->add($this->getFormFieldMock('bar[]'));
$registry->add($this->getFormFieldMock('bar[baz]'));
$this->assertEquals(
array('foo[0]', 'foo[1]', 'bar[5]', 'bar[6]', 'bar[baz]'),
array_keys($registry->all())
);
}
public function testFormRegistrySetValues()
{
$registry = new FormFieldRegistry();
$registry->add($f2 = $this->getFormFieldMock('foo[2]'));
$registry->add($f3 = $this->getFormFieldMock('foo[3]'));
$registry->add($fbb = $this->getFormFieldMock('foo[bar][baz]'));
$f2
->expects($this->exactly(2))
->method('setValue')
->with(2)
;
$f3
->expects($this->exactly(2))
->method('setValue')
->with(3)
;
$fbb
->expects($this->exactly(2))
->method('setValue')
->with('fbb')
;
$registry->set('foo[2]', 2);
$registry->set('foo[3]', 3);
$registry->set('foo[bar][baz]', 'fbb');
$registry->set('foo', array(
2 => 2,
3 => 3,
'bar' => array(
'baz' => 'fbb'
)
));
}
protected function getFormFieldMock($name, $value = null)
{
$field = $this
->getMockBuilder('Symfony\\Component\\DomCrawler\\Field\\FormField')
->setMethods(array('getName', 'getValue', 'setValue', 'initialize'))
->disableOriginalConstructor()
->getMock()
;
$field
->expects($this->any())
->method('getName')
->will($this->returnValue($name))
;
$field
->expects($this->any())
->method('getValue')
->will($this->returnValue($value))
;
return $field;
}
protected function createForm($form, $method = null, $currentUri = null)
{
$dom = new \DOMDocument();
$dom->loadHTML('<html>'.$form.'</html>');
$nodes = $dom->getElementsByTagName('input');
$xPath = new \DOMXPath($dom);
$nodes = $xPath->query('//input | //button');
if (null === $currentUri) {
$currentUri = 'http://example.com/';
}
return new Form($nodes->item($nodes->length - 1), $currentUri, $method);
}
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DomCrawler\Tests;
use Symfony\Component\DomCrawler\Link;
class LinkTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \LogicException
*/
public function testConstructorWithANonATag()
{
$dom = new \DOMDocument();
$dom->loadHTML('<html><div><div></html>');
new Link($dom->getElementsByTagName('div')->item(0), 'http://www.example.com/');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testConstructorWithAnInvalidCurrentUri()
{
$dom = new \DOMDocument();
$dom->loadHTML('<html><a href="/foo">foo</a></html>');
new Link($dom->getElementsByTagName('a')->item(0), 'example.com');
}
public function testGetNode()
{
$dom = new \DOMDocument();
$dom->loadHTML('<html><a href="/foo">foo</a></html>');
$node = $dom->getElementsByTagName('a')->item(0);
$link = new Link($node, 'http://example.com/');
$this->assertEquals($node, $link->getNode(), '->getNode() returns the node associated with the link');
}
public function testGetMethod()
{
$dom = new \DOMDocument();
$dom->loadHTML('<html><a href="/foo">foo</a></html>');
$node = $dom->getElementsByTagName('a')->item(0);
$link = new Link($node, 'http://example.com/');
$this->assertEquals('GET', $link->getMethod(), '->getMethod() returns the method of the link');
$link = new Link($node, 'http://example.com/', 'post');
$this->assertEquals('POST', $link->getMethod(), '->getMethod() returns the method of the link');
}
/**
* @dataProvider getGetUriTests
*/
public function testGetUri($url, $currentUri, $expected)
{
$dom = new \DOMDocument();
$dom->loadHTML(sprintf('<html><a href="%s">foo</a></html>', $url));
$link = new Link($dom->getElementsByTagName('a')->item(0), $currentUri);
$this->assertEquals($expected, $link->getUri());
}
public function getGetUriTests()
{
return array(
array('/foo', 'http://localhost/bar/foo/', 'http://localhost/foo'),
array('/foo', 'http://localhost/bar/foo', 'http://localhost/foo'),
array('
/foo', 'http://localhost/bar/foo/', 'http://localhost/foo'),
array('/foo
', 'http://localhost/bar/foo', 'http://localhost/foo'),
array('foo', 'http://localhost/bar/foo/', 'http://localhost/bar/foo/foo'),
array('foo', 'http://localhost/bar/foo', 'http://localhost/bar/foo'),
array('', 'http://localhost/bar/', 'http://localhost/bar/'),
array('#', 'http://localhost/bar/', 'http://localhost/bar/#'),
array('#bar', 'http://localhost/bar/#foo', 'http://localhost/bar/#bar'),
array('?a=b', 'http://localhost/bar/', 'http://localhost/bar/?a=b'),
array('http://login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'),
array('https://login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'),
array('mailto:foo@bar.com', 'http://localhost/foo', 'mailto:foo@bar.com'),
// tests schema relative URL (issue #7169)
array('//login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'),
array('//login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'),
array('?foo=2', 'http://localhost?foo=1', 'http://localhost?foo=2'),
array('?foo=2', 'http://localhost/?foo=1', 'http://localhost/?foo=2'),
array('?foo=2', 'http://localhost/bar?foo=1', 'http://localhost/bar?foo=2'),
array('?foo=2', 'http://localhost/bar/?foo=1', 'http://localhost/bar/?foo=2'),
array('?bar=2', 'http://localhost?foo=1', 'http://localhost?bar=2'),
array('foo', 'http://login.foo.com/bar/baz?/query/string', 'http://login.foo.com/bar/foo'),
array('.', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'),
array('./', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'),
array('./foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/foo'),
array('..', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'),
array('../', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'),
array('../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/foo'),
array('../..', 'http://localhost/foo/bar/baz', 'http://localhost/'),
array('../../', 'http://localhost/foo/bar/baz', 'http://localhost/'),
array('../../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo'),
array('../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'),
array('../bar/../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'),
array('../bar/./../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'),
array('../../', 'http://localhost/', 'http://localhost/'),
array('../../', 'http://localhost', 'http://localhost/'),
array('/foo', 'file:///', 'file:///foo'),
array('/foo', 'file:///bar/baz', 'file:///foo'),
array('foo', 'file:///', 'file:///foo'),
array('foo', 'file:///bar/baz', 'file:///bar/foo'),
);
}
}

View File

@@ -0,0 +1,37 @@
{
"name": "symfony/dom-crawler",
"type": "library",
"description": "Symfony DomCrawler Component",
"keywords": [],
"homepage": "http://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/css-selector": "~2.0"
},
"suggest": {
"symfony/css-selector": ""
},
"autoload": {
"psr-0": { "Symfony\\Component\\DomCrawler\\": "" }
},
"target-dir": "Symfony/Component/DomCrawler",
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Symfony DomCrawler Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>