diff --git a/src/Symfony/Component/DomCrawler/CHANGELOG.md b/src/Symfony/Component/DomCrawler/CHANGELOG.md index ef365a8fd9..5a7da3412f 100644 --- a/src/Symfony/Component/DomCrawler/CHANGELOG.md +++ b/src/Symfony/Component/DomCrawler/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * added schema relative URL support to links + * added support for HTML5 'form' attribute 2.2.0 ----- diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index 6eaf246c76..d30a8b1ddc 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -331,7 +331,9 @@ class Form extends Link implements \ArrayAccess } /** - * Sets current \DOMNode instance. + * Sets the node for the form. + * + * Expects a 'submit' button \DOMNode and finds the corresponding form element. * * @param \DOMNode $node A \DOMNode instance * @@ -341,8 +343,18 @@ class Form extends Link implements \ArrayAccess { $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 { - // use the ancestor form element if (null === $node = $node->parentNode) { throw new \LogicException('The selected node does not have a form ancestor.'); } @@ -366,30 +378,47 @@ class Form extends Link implements \ArrayAccess $root->appendChild($button); $xpath = new \DOMXPath($document); - foreach ($xpath->query('descendant::input | descendant::button | descendant::textarea | descendant::select', $root) as $node) { - if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { - continue; - } + // 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); + } - $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)); + // 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)); + } + } } diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index 8aac5d082f..05fead77f4 100644 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -393,6 +393,9 @@ EOF $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() @@ -427,10 +430,17 @@ EOF public function testForm() { - $crawler = $this->createTestCrawler('http://example.com/bar/')->selectButton('FooValue'); + $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(array('FooName' => 'FooBar'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument'); + $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(); @@ -576,12 +586,16 @@ EOF GetLink -
+ +