phpunit selenium2 - select multiple elements - php

I have a lot of 'h2' elements with 'a' inside.
All I want is just get all the 'a' in the 'h2'
when I try something like this:
public function testBreakingNewsH2(){
$this->url('index.php');
$h2Elements = $this->byCssSelector('h2')->byCssSelector('a')->text();
$this->assertEquals('Breaking News', $h2Elements);
}
I get only the first 'a' inside 'h2'.
I need to check that all 'h2' links are exists (get all 'h2' elements that contains 'a')
I tried something like this:
public function testHomePgaeH2(){
$this->url('index.php');
$h2Elements = $this->elements($this->using('css selector')->value('h2'));
$this->assertContains('Breaking News', $h2Elements);
$this->assertContains('Analysis', $h2Elements);
$this->assertContains('Calendar', $h2Elements);
$this->assertContains('Studies', $h2Elements);
}
this not works, this is the best example that I found for my issue.
of course I can try something like this:
$this->assrtRegExp('/xxxx/i', $this->source());
but I want make it clean as possible without taking all the source.
please advise,
thanks.

You get a list of elements by css selector with this code:
$elements = $this->elements($this->using('css selector')->value('h2 a'));
foreach ($elements as $i=>$element){
echo $element->text() . "\n";
}
If you want to check that all h2 contain a, you can find all elements by css selector h2 and count them then find by h2 a and count again. Then compare. Also you can iterate all h2 a elements and check their urls by array or regexp. It depends on what you want.

Related

Using simpleXmlElement, need to add 3 attributes to an element and the output element is missing namespace

Im working to php SimpleXMLElement to build an xml sitemap and I have 2 questions about it.
As I'm working with a multilanguage domain I need to include the hreflang element see https://support.google.com/webmasters/answer/2620865?hl=en for reference.
This element has 3 attributes 'rel', 'href' & 'hreflang'.
How do we set this element and add custom values to it?
// example
foreach($array as $value ){
$item->addChild('xhtml:link' , '//takes no value');
// needed output
<xhtml:link href="http://www.example.com/path-to-file" hreflang="de" rel="alternate"/>
}
Also when using
->addChild('xhtml:link')
it will output
<link/>
and NOT
<xhtml:link/>
Yes im using the correct urlset attributes(xmlns:xhtml="http://www.w3.org/1999/xhtml").
When adding a new element using addChild() there is a third parameter for the namespace. Also you add the attributes using - addAttribute(). So create the element, then add each attribute one at a time...
foreach($array as $value ){
$newElement = $item->addChild('link' , '//takes no value', 'xhtml');
$newElement->addAttribute( "href", "http://www.example.com/path-to-file");
$newElement->addAttribute( "hreflang", "de");
$newElement->addAttribute( "rel", "alternate");
// needed output
//<xhtml:link href="http://www.example.com/path-to-file" hreflang="de" rel="alternate"/>
}

PHP simplexml xpath search for value in an ELEMENT containing tab delimited text?

How to do a PHP simplexml xpath search for text value in a tab delimited ELEMENT and returning text from that same element at a different offset from where the search text offset?
Lets say I wish to find the DATA element containing a Value of '2' and return the LongValue 'Academy'.
The xml document is in the following format
<METADATA Resource="Property" Lookup="Area">
<COLUMNS>->fieldname *(->fieldname)-></COLUMNS>
*(<DATA>->fielddata *(->fielddata)-></DATA>)
</METADATA>
Note: ignore spaces
*() means 1 or more
-> is tab chr(9)
In the example below the COLUMNS element contains three column names (LongValue, ShortValue, Value), which can be in any order.
Each DATA element has 3 corresponding tab delimited text values, for example the first DATA element below contains
LongVlaue = 'Salado'
ShortValue = 'Sal'
Value = '5'
Here is the XML document
<METADATA Resource="Property" Lookup="Area">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA> Salado Sal 5 </DATA>
<DATA> Academy Aca 2 </DATA>
<DATA> Rogers Rog 1 </DATA>
<DATA> Bartlett Bar 4 </DATA>
</METADATA>
Note: the COLUMNS and DATA elements has text tab delimited for 3 columns where each column starts with a tab followed by text, then one last tab at the end
Here's what I think:
1.) Preferably find the offset for the column named 'Value' from the COLUMNS element before trying to find the corresponding text from the DATA element because the ‘Value’ column can be in any order, however the text in the DATA elements will be in that order.
2.) Search for a DATA element containing text in the 'Value' column and return the text from the 'LongValue'.
Here's a example of an xpath search that some what works but is flawed because it does not take in account the offset for the Value column in the COLUMNS element so it can properly find the corresponding (correct) position of the ‘Value’ column in the DATA element.
Here's a code snip-it:
$xml_text = ‘the xml document above’;
$xml = simplexml_load_string($xml_text); //load the xml document
$resource = 'Property'; //value for the Resource attribute METADATA.
$lookup = 'Area'; //value for the Lookup attribute in METADATA
$value = '2'; //the needle we are looking for
$find = "\t" . $value . "\t";
/*
adding tabs before and after the $value may be flawed, although each
column starts with a tab followed by text, only the last column has
the an extra tab. Not sure this would work properly if the column
was in the middle, or if the ELEMENT happened to have multiple $value
in the same element. */
/*
Search for a specific METADATA element with matching
Resource and Lookup attributes */
$node = $this->xml->xpath(
"//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']"
."/DATA[contains(., '{$find}')]"
);
$x = explode("\t", (string) trim($node[0])); //convert the tab delimited
//string to an array
echo print_r($x,true); //this shows what the array would look like,
//with out the trim there would be empty
//first and last array elements
Array
(
[0] => Academy
[1] => Aca
[2] => 2
)
$LongValue = $x[0]; //assuming the LongValue is in the first column
echo $LongValue; //this shows the LongValue retuned
Academy
Thanks for any help!
Update... After posting, came up with this…
//get index of 'Values' column from COLUMNS element
$node = $this->xml->xpath(
"//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']"
."/COLUMNS");
if($node) {
//array of column names
$columns = explode("\t", strtolower((string) trim($node[0])));
$long_value_index = array_search('longvalue', $columns);
} else {
echo 'not found';
exit;
}
Now with the $index this could return the LongValue from the proper offset
$LongValue = $x[$long_value_index];
Any thoughts
You are already quite far and you have well analyzed the data you need to deal with. Also how you say you want to parse the data looks very well for me. The only thing that probably can be a little improved is that you take care to not do too much at once.
One way to do so is to divide the problem(s) into smaller ones. I will show you how that works putting code into multiple functions and methods. But lets start with a single function, this goes step-by-step, so you can try to follow the examples to build this up.
One way to separate problems in PHP is to use functions. For example, write one function to search in the XML document, this makes the code look a better and more speaking:
/**
* search metadata element
*
*
* #param SimpleXMLElement $xml
* #param string $resource metadata attribute
* #param string $lookup metadata attribute
* #param string $value search value
*
* #return SimpleXMLElement
*/
function metadata_search(SimpleXMLElement $xml, $resource, $lookup, $value) {
$xpath = "//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']"
."/DATA[contains(., '{$find}')]";
list($element)= $xml->xpath($xpath);
return $element;
}
So now you can easily search the document, the parameters are named and documented. All that it is needed is to call the function and get the return value:
$data = metadata_search($xml, 'Property', 'Area', 2);
This might not be the perfect function, but it is an example already. Next to functions you can also create objects. Objects are functions that have their own context. That's why those functions are called methods then, they belong to the object. Like the xpath() method of the SimpleXMLElement.
If you see the function above, the first parameter is the $xml object. On that the xpath method is then executed. In the end what this function really does is creating and running the xpath query based on the input variables.
If we could bring that function directly into the $xml object, we would not need to pass that any longer as first parameter. That is the next step and it works by extending SimpleXMLElement. We just add one new method that does the search and the method is pretty much the same as above. We also extend from SimpleXMLElement which means we create a sub-type of it: That is all it has already plus that new method you add:
class MetadataElement extends SimpleXMLElement
{
/**
* #param string $resource metadata attribute
* #param string $lookup metadata attribute
* #param string $value search value
*
* #return SimpleXMLElement
*/
public function search($resource, $lookup, $value) {
$xpath = "//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']"
."/DATA[contains(., '{$value}')]";
list($element)= $this->xpath($xpath);
return $element;
}
}
To get this to life, we need to provide the name of this class when loading the XML string. Then the search method can be called directly:
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 2);
Voila, the search is now with the SimpleXMLElement!
But what to do with this $data? It's just an XML element and it still contains the tabs.
Even more bad, the context is lost: To which metadata column does this belong to? That is your problem. So we need to solve this next - but how?
Honestly, there are many ways to do that. One Idea I had was to create a table object out of the XML based on a metadata element:
list($metadata) = $xml->xpath('//METADATA[1]');
$csv = new CsvTable($metadata);
echo $csv;
Even with nice debug output:
+---------+----------+-----+
|LongValue|ShortValue|Value|
+---------+----------+-----+
|Salado |Sal |5 |
+---------+----------+-----+
|Academ |Aca |2 |
+---------+----------+-----+
|Rogers |Rog |1 |
+---------+----------+-----+
|Bartlett |Bar |4 |
+---------+----------+-----+
But that is somehow a lot of work if you're probably not fluent with programming objects so building a whole table model on it's own is maybe a bit much.
Therefore I had the idea: Why not continue to use the XML object you already use and change the XML in there a bit to have it in a better format for your purposes. From:
<METADATA Resource="Property" Lookup="Area">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA> Salado Sal 5 </DATA>
To:
<METADATA Resource="Property" Lookup="Area" transformed="1">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA>
<LongValue>Salado</LongValue><ShortValue>Sal</ShortValue><Value>5</Value>
</DATA>
This would allow to not only search per a specific column name but also to find the other values in the data element. If the search return the $data element:
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 5);
echo $data->Value; # 5
echo $data->LongValue; # Salado
If we leave an additional attribute with the metadata-element we can convert these elements while we search. If some data is found and the element not yet converted, it will be converted.
Because we all do this inside the search method, the code using the search method must not change much (if not even not at all - depends a bit on the detailed needs you have, I might not have fully grasped those, but I think you get the idea). So let's put this to work. Because we don't want to do this all at once, we create multiple new methods to:
transform a metadata element
search inside the original element (this code we have already, we just move it)
Along the way we will also create methods we deem helpful, you will notice that this is also partly code that you have written already (like in search()), it is just placed now inside the $xml object - where it more naturally belongs.
Then finally these new methods will be put together in the existing search() method.
So first of all, we create a helper method to parse this tabbed line into an array. It's basically your code, you do not need the string cast in front of trim, that is the only difference. Because this function is only needed inside, we make it private:
private function asExplodedString() {
return explode("\t", trim($this));
}
By its name it is clear what it does. It gives back the tab-exploded array of itself. If you remember, we are inside $xml so now every xml-element has this method. If you do not full understand this yet, just go on, you can see how it works right below, we only add one more method as a helper:
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
This function allows us to retrieve the parent element of an element. This is useful because if we find a data element we want to transform the metadata element which is the parent. And because this function is of general use, I have chosen to make it public. So it can be used also in outside code. It solves a common problem and therefore is not of that specific nature like the explode method.
So now we want to transform a metadata element. It will take some more lines of code as these two helper methods above though, but thanks to those things will not be complicated.
We just assume that the element this method is called on is the metadata element. We do not add checks here to keep the code small. As this is a private function again, we even do not need to check: If this method is invoked on the wrong element, the fault had been done inside the class itself - not from outside code. This is also a nice example why I use private methods here, it's much more specific.
So what we do now with the metadata element is actually quite simple: We fetch the column element inside, explode the column names, and then we go over each data-element, explode the data as well, then empty the data-element only to add the column-named children to it. Finally we add an attribute to mark the element as transformed:
private function transform() {
$columns = $this->COLUMNS->asExplodedString();
foreach ($this->DATA as $data) {
$values = $data->asExplodedString();
$data[0] = ''; # set the string of the element (make <DATA></DATA> empty)
foreach ($columns as $index => $name) {
$data->addChild($name, $values[$index]);
}
}
$this['transformed'] = 1;
}
Okay. Now what gives? Let's test this. To do that we modify the existing search function to return the transformed data element - by adding a single line of code:
public function search($resource, $lookup, $value) {
$xpath = "//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']"
. "/DATA[contains(., '{$value}')]";
list($element) = $this->xpath($xpath);
$element->getParent()->transform();
###################################
return $element;
}
And then we output it as XML:
$data = $xml->search('Property', 'Area', 2);
echo $data->asXML();
This now gives the following output (beautified, it's on a single line normally):
<DATA>
<LongValue>Academ</LongValue>
<ShortValue>Aca</ShortValue>
<Value>2</Value>
</DATA>
And let's also check that the new attribute is set and all other data-elements of that metadata-table/block are transformed as well:
echo $data->getParent()->asXML();
And the output (beautified) as well:
<METADATA Resource="Property" Lookup="Area" transformed="1">
<COLUMNS> LongValue ShortValue Value </COLUMNS>
<DATA>
<LongValue>Salado</LongValue>
<ShortValue>Sal</ShortValue>
<Value>5</Value>
</DATA>
...
This shows that the code works as intended. This might already solve your issue. E.g. if you always search for a number and the other columns do not contain numbers and you only need to search one per metadata block. However likely not, therefore the search function needs to be changed to perform the correct search and transform internally.
This time again we make use of the $this to put a method on the concrete XML element. Two new methhods: One to get a Metadata element based on it's attributes:
private function getMetadata($resource, $lookup) {
$xpath = "//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']";
list($metadata) = $this->xpath($xpath);
return $metadata;
}
And one to search a specific column of a metadata element:
private function searchColumn($column, $value) {
return $this->xpath("DATA[{$column}[contains(., '{$value}')]]");
}
These two methods are then used in the main search method. It will be slightly changed by first looking up the metadata element by its attributes. Then it will be checked if the transformation is needed and then the search by the value column is done:
public function search($resource, $lookup, $value)
{
$metadata = $this->getMetadata($resource, $lookup);
if (!$metadata['transformed']) {
$metadata->transform();
}
list($element) = $metadata->searchColumn('Value', $value);
return $element;
}
And now the new way of searching is finally done. It now searches only in the right column and the transformation will be done on the fly:
$xml = simplexml_load_string($xmlString, 'MetadataElement');
$data = $xml->search('Property', 'Area', 2);
echo $data->LongValue, "\n"; # Academ
Now that looks nice and it looks as if it is totally easy to use! All the complexity went into MetadataElement. And how does it look like at a glance?
/**
* MetadataElement - Example for extending SimpleXMLElement
*
* #link http://stackoverflow.com/q/16281205/367456
*/
class MetadataElement extends SimpleXMLElement
{
/**
* #param string $resource metadata attribute
* #param string $lookup metadata attribute
* #param string $value search value
*
* #return SimpleXMLElement
*/
public function search($resource, $lookup, $value)
{
$metadata = $this->getMetadata($resource, $lookup);
if (!$metadata['transformed']) {
$metadata->transform();
}
list($element) = $metadata->searchColumn('Value', $value);
return $element;
}
private function getMetadata($resource, $lookup) {
$xpath = "//METADATA[#Resource='{$resource}' and #Lookup='{$lookup}']";
list($metadata) = $this->xpath($xpath);
return $metadata;
}
private function searchColumn($column, $value) {
return $this->xpath("DATA[{$column}[contains(., '{$value}')]]");
}
private function asExplodedString() {
return explode("\t", trim($this));
}
public function getParent() {
list($parent) = $this->xpath('..') + array(0 => NULL);
return $parent;
}
private function transform() {
$columns = $this->COLUMNS->asExplodedString();
foreach ($this->DATA as $data) {
$values = $data->asExplodedString();
$data[0] = ''; # set the string of the element (make <DATA></DATA> empty)
foreach ($columns as $index => $name) {
$data->addChild($name, $values[$index]);
}
}
$this['transformed'] = 1;
}
}
Not too bad either. Many small methods that just have some little lines of code, that is (rel.) easy to follow!
So I hope this gives some inspiration, I know this was a quite some text to read. Have fun!

The select option in php

If I have a plugin in my server account which has this code:
function script_options() {
$scripts = array(
'Forum' => array(
'403' => 'phpBB2',
'5' => 'phpBB3',
'417' => 'Simple Machines Forum',
),
'Blog' => array(
'410' => 'b2evolution',
'418' => 'Movable Type',
'409' => 'Textpattern',
'400' => 'WordPress',
),
'Wiki' => array(
'413' => 'DokuWiki',
),
);
$result = '<option value="">'.__rp('none','none').'</option>';
foreach ($scripts as $group => $list) {
$result .= "<optgroup label=\"$group\">";
foreach ($list as $script_key => $script_value) {
$result .= "<option value=\"$script_key\"".($script_key == isset($_GET['script'])?$_GET['script']:false ? ' selected="selected"':'').">$script_value</option>";
}
$result .= '</optgroup>';
}
return $result;
}
How can I make the first choice be ('400' 'Wordpress'); if the user did not choose anything it chooses Wordpress by itself.
It looks like you don't fully understand what this code does, so let me boil it down for you.
Let's start at the end and work our way backwards: When the function returns, the $result variable contains an HTML fragment with a bunch of <optgroup> tags in it, and those contain <option> tags. I assume this HTML gets pasted into a <select> tag somewhere outside this function (BTW, <select> is an HTML thing; PHP has a select() function that is completely unrelated to this, so to avoid confusion, don't refer to HTML's <select> as "select option in PHP").
The foreach loops right before that construct the $result value by concatenating individual chunks of HTML, and those are in turn derived from the nested associative array declared at the beginning of the function. If you follow those loops carefully, you'll see that the tree structure of the resulting HTML follows that of the nested array: each top-level element becomes an <optgroup>, and the name is derived from the array key; each second-level element becomes an <option>, where the key goes into the value attribute (which determines the value used when the containing form is submitted), and the value goes into the user-visible tag content. The array elements are visited in order, so what comes first in the array also comes first in the resulting HTML.
There are two things you need to know about <select> in this context: first, you can define which option is selected by default by adding a selected tag to it (the standard way to express this is <option selected="selected">...</option>); there should be at most one selected option in your <select>. And second, if none of your options has a select attribute, the first one becomes the default.
Now, what does this mean for you? Simple: you can either make it so that your code sets the selected attribute on the WordPress entry; this way, your options remain ordered exactly as they are now, optgroups and all, but WordPress will be preselected. Or you can put the WordPress one as the very first element, which puts it first in the <select>, and should get it preselected because no option has a selected attribute.

Zend Form - How do I remove the optgroup label

I know this has been asked before, but I just cant seem to find an answer... or solution.
I have many select boxes using 'multiselect'. The dropdowns are being populated from the database, and the first value in the array is always 'Select One'. This I cannot change, I am rewriting an app and am not changing the database at all.
Everything works just fine, but they always come out as 'optgroup' tags with a label, which always puts a '0' at the top of the list. The boxes always say 'Select One', which is great, but when expanded you see the '0' at the top... which is the 'label' attribute to the optgroup tag.
I do it all somehting like this...
$Criteria = new Criteria();
$Criteria->add( DictionaryPeer::CATEGORY, 'Progress Notes: Program Status' );
$Criteria->addAscendingOrderByColumn( 'Ordinal' );
$ProgramStatuses = DictionaryPeer::doSelect($Criteria);
$ProgramStatusList = array();
foreach ($ProgramStatuses as $ProgramStatus) {
$ProgramStatusList [ $ProgramStatus->getDictionaryID() ] = $ProgramStatus->getWord();
}
$form->programstatus->addMultiOptions( array(
$ProgramStatusList ));
echo $form->programstatus->renderLabel() . $form->programstatus->renderViewHelper();
I just want to remove the '0' for presentation purposes only...
Any help is always appreciated...
Thanks!
If you want to get rid of the OPTGROUP, you just need to pass a simple array as parameter to addMultiOptions() as follows:
$form->programstatus->addMultiOptions($ProgramStatusList);
Because if you pass a multidimensional array, Zend_Form will indirectly consider each index of the parent array as a group of options (using the FormElement View Helper).

use different string in function?

I am a total NOOB in programming (but this is only my second question on stackoverflow :-) ).
By a foreach function I get 5 different string values for $Loncoord, $Latcoord, $gui;
this I can see with the print_r in the code written below:
"-5.68166666667","+24.6513888889","IMG_3308",
But I now want to create 5 different markers in the $map->addMarkerByCoords (function is it ?).
print_r ("$Loncoord");
print_r ("$Latcoord");
print_r ("$gui");
$map->addMarkerByCoords("$Loncoord","$Latcoord","$gui",'OldChicago');
Is this possible?
Do I need to put them in a array and call these in the (function ?) or do I need to use a foreach function?
I tried both for a week now but I can't get it working.
Can you help me?
The answers you produced gave me a turn in the right direction.
Thank you for the quick responses and the explaining part.
But for the addMarkerByCoord (function! (stupid me)) I found this in the googlemaps API:
function addMarkerByCoords($lon,$lat,$title = '',$html = '',$tooltip = '') {
$_marker['lon'] = $lon;
$_marker['lat'] = $lat;
$_marker['html'] = (is_array($html) || strlen($html) > 0) ? $html : $title;
$_marker['title'] = $title;
$_marker['tooltip'] = $tooltip;
$this->_markers[] = $_marker;
$this->adjustCenterCoords($_marker['lon'],$_marker['lat']);
// return index of marker
return count($this->_markers) - 1;
}
It depends on the implementation of map::addMarkerByCoords()
The method (not a function) name, and its signature, suggests that you are only able to add one coord at a time. But to be sure you'ld need to know the methods true signature. So the question is: does the method allow arrays as arguments?
Usually, a method that allows you to add multiple items at once, has the plural name of the intended action in it's name:
map::addMarkersByCoords() // note the s after Marker
If the 'map' class is your own implementation, you are free to implement it the way you like of course, but in that case keep the descriptive names of the methods in mind. So, add one marker:
map::addMarkerByCoords()
Add multiple markers at once:
map::addMarkersByCoords()
Typically you would implement the plural method as something like this:
public function addMarkersByCoords( array $markers )
{
foreach( $markers as $marker )
{
$this->addMarkerByCoord( $marker[ 'long' ], $marker[ 'lat' ], $marker[ 'img ' ], $marker[ 'name' ] );
}
}
Basically, the plural method accepts one array, and adds each individual marker by calling the singular method.
If you wanna get even more OOP, you could implement the plural and singular method to accept (an array of) Marker objects. But that is not particalarly relevant for this discussion.
Also, the suggested expantion of the Map's interface with a plural method doesn't nessecarily mean you can't add multiple markers outside the object with calling the singular method in a foreach loop. It's up to your preference really.
If you want to call the addMarkerByCoords for 5 times with 5 different values for each parameter then you can build an array for every parameter and then iterate with the foreach function:
$Loncoord=array(1,2,3,4,5);
$Latcoord=array(1,2,3,4,5);
$gui=array(1,2,3,4,5);
$city=array('OldChicago','bla','bla','bla','bla');
foreach($Loncoord as $k=>$v)
$map->addMarkerByCoords($Loncoord[$k],$Latcoord[$k],$gui[$k],$city[$k]);
Try losing some of the quotes...
$map->addMarkerByCoords($Loncoord,$Latcoord,$gui,'OldChicago');
To answer the question properly though, we would need to know what addMarkerByCoords was expecting you to pass to it.

Categories