Source for file page.php

Documentation is available at page.php

  1. <?php
  2. /**
  3.  *  Base include file for SimpleTest
  4.  *  @package    SimpleTest
  5.  *  @subpackage WebTester
  6.  *  @version    $Id: page.php 1672 2008-03-02 04:47:34Z edwardzyang $
  7.  */
  8.  
  9. /**#@+
  10.     *   include other SimpleTest class files
  11.     */
  12. require_once(dirname(__FILE__'/http.php');
  13. require_once(dirname(__FILE__'/parser.php');
  14. require_once(dirname(__FILE__'/tag.php');
  15. require_once(dirname(__FILE__'/form.php');
  16. require_once(dirname(__FILE__'/selector.php');
  17. /**#@-*/
  18.  
  19.  *    Creates tags and widgets given HTML tag
  20.  *    attributes.
  21.  *    @package SimpleTest
  22.  *    @subpackage WebTester
  23.  */
  24.  
  25.     /**
  26.      *    Factory for the tag objects. Creates the
  27.      *    appropriate tag object for the incoming tag name
  28.      *    and attributes.
  29.      *    @param string $name        HTML tag name.
  30.      *    @param hash $attributes    Element attributes.
  31.      *    @return SimpleTag          Tag object.
  32.      *    @access public
  33.      */
  34.     function createTag($name$attributes{
  35.         static $map array(
  36.                 'a' => 'SimpleAnchorTag',
  37.                 'title' => 'SimpleTitleTag',
  38.                 'base' => 'SimpleBaseTag',
  39.                 'button' => 'SimpleButtonTag',
  40.                 'textarea' => 'SimpleTextAreaTag',
  41.                 'option' => 'SimpleOptionTag',
  42.                 'label' => 'SimpleLabelTag',
  43.                 'form' => 'SimpleFormTag',
  44.                 'frame' => 'SimpleFrameTag');
  45.         $attributes $this->_keysToLowerCase($attributes);
  46.         if (array_key_exists($name$map)) {
  47.             $tag_class $map[$name];
  48.             return new $tag_class($attributes);
  49.         elseif ($name == 'select'{
  50.             return $this->_createSelectionTag($attributes);
  51.         elseif ($name == 'input'{
  52.             return $this->_createInputTag($attributes);
  53.         }
  54.         return new SimpleTag($name$attributes);
  55.     }
  56.  
  57.     /**
  58.      *    Factory for selection fields.
  59.      *    @param hash $attributes    Element attributes.
  60.      *    @return SimpleTag          Tag object.
  61.      *    @access protected
  62.      */
  63.     function _createSelectionTag($attributes{
  64.         if (isset($attributes['multiple'])) {
  65.             return new MultipleSelectionTag($attributes);
  66.         }
  67.         return new SimpleSelectionTag($attributes);
  68.     }
  69.  
  70.     /**
  71.      *    Factory for input tags.
  72.      *    @param hash $attributes    Element attributes.
  73.      *    @return SimpleTag          Tag object.
  74.      *    @access protected
  75.      */
  76.     function _createInputTag($attributes{
  77.         if (isset($attributes['type'])) {
  78.             return new SimpleTextTag($attributes);
  79.         }
  80.         $type strtolower(trim($attributes['type']));
  81.         $map array(
  82.                 'submit' => 'SimpleSubmitTag',
  83.                 'image' => 'SimpleImageSubmitTag',
  84.                 'checkbox' => 'SimpleCheckboxTag',
  85.                 'radio' => 'SimpleRadioButtonTag',
  86.                 'text' => 'SimpleTextTag',
  87.                 'hidden' => 'SimpleTextTag',
  88.                 'password' => 'SimpleTextTag',
  89.                 'file' => 'SimpleUploadTag');
  90.         if (array_key_exists($type$map)) {
  91.             $tag_class $map[$type];
  92.             return new $tag_class($attributes);
  93.         }
  94.         return false;
  95.     }
  96.  
  97.     /**
  98.      *    Make the keys lower case for case insensitive look-ups.
  99.      *    @param hash $map   Hash to convert.
  100.      *    @return hash       Unchanged values, but keys lower case.
  101.      *    @access private
  102.      */
  103.     function _keysToLowerCase($map{
  104.         $lower array();
  105.         foreach ($map as $key => $value{
  106.             $lower[strtolower($key)$value;
  107.         }
  108.         return $lower;
  109.     }
  110. }
  111.  
  112. /**
  113.  *    SAX event handler. Maintains a list of
  114.  *    open tags and dispatches them as they close.
  115.  *    @package SimpleTest
  116.  *    @subpackage WebTester
  117.  */
  118.     var $_tags;
  119.     var $_page;
  120.     var $_private_content_tag;
  121.  
  122.     /**
  123.      *    Sets the builder up empty.
  124.      *    @access public
  125.      */
  126.     function SimplePageBuilder({
  127.         $this->SimpleSaxListener();
  128.     }
  129.     
  130.     /**
  131.      *    Frees up any references so as to allow the PHP garbage
  132.      *    collection from unset() to work.
  133.      *    @access public
  134.      */
  135.     function free({
  136.         unset($this->_tags);
  137.         unset($this->_page);
  138.         unset($this->_private_content_tags);
  139.     }
  140.  
  141.     /**
  142.      *    Reads the raw content and send events
  143.      *    into the page to be built.
  144.      *    @param $response SimpleHttpResponse  Fetched response.
  145.      *    @return SimplePage                   Newly parsed page.
  146.      *    @access public
  147.      */
  148.     function &parse($response{
  149.         $this->_tags = array();
  150.         $this->_page = &$this->_createPage($response);
  151.         $parser &$this->_createParser($this);
  152.         $parser->parse($response->getContent());
  153.         $this->_page->acceptPageEnd();
  154.         return $this->_page;
  155.     }
  156.  
  157.     /**
  158.      *    Creates an empty page.
  159.      *    @return SimplePage        New unparsed page.
  160.      *    @access protected
  161.      */
  162.     function &_createPage($response{
  163.         $page &new SimplePage($response);
  164.         return $page;
  165.     }
  166.  
  167.     /**
  168.      *    Creates the parser used with the builder.
  169.      *    @param $listener SimpleSaxListener   Target of parser.
  170.      *    @return SimpleSaxParser              Parser to generate
  171.      *                                          events for the builder.
  172.      *    @access protected
  173.      */
  174.     function &_createParser(&$listener{
  175.         $parser &new SimpleHtmlSaxParser($listener);
  176.         return $parser;
  177.     }
  178.     
  179.     /**
  180.      *    Start of element event. Opens a new tag.
  181.      *    @param string $name         Element name.
  182.      *    @param hash $attributes     Attributes without content
  183.      *                                 are marked as true.
  184.      *    @return boolean             False on parse error.
  185.      *    @access public
  186.      */
  187.     function startElement($name$attributes{
  188.         $factory &new SimpleTagBuilder();
  189.         $tag $factory->createTag($name$attributes);
  190.         if ($tag{
  191.             return true;
  192.         }
  193.         if ($tag->getTagName(== 'label'{
  194.             $this->_page->acceptLabelStart($tag);
  195.             $this->_openTag($tag);
  196.             return true;
  197.         }
  198.         if ($tag->getTagName(== 'form'{
  199.             $this->_page->acceptFormStart($tag);
  200.             return true;
  201.         }
  202.         if ($tag->getTagName(== 'frameset'{
  203.             $this->_page->acceptFramesetStart($tag);
  204.             return true;
  205.         }
  206.         if ($tag->getTagName(== 'frame'{
  207.             $this->_page->acceptFrame($tag);
  208.             return true;
  209.         }
  210.         if ($tag->isPrivateContent(&& isset($this->_private_content_tag)) {
  211.             $this->_private_content_tag = &$tag;
  212.         }
  213.         if ($tag->expectEndTag()) {
  214.             $this->_openTag($tag);
  215.             return true;
  216.         }
  217.         $this->_page->acceptTag($tag);
  218.         return true;
  219.     }
  220.  
  221.     /**
  222.      *    End of element event.
  223.      *    @param string $name        Element name.
  224.      *    @return boolean            False on parse error.
  225.      *    @access public
  226.      */
  227.     function endElement($name{
  228.         if ($name == 'label'{
  229.             $this->_page->acceptLabelEnd();
  230.             return true;
  231.         }
  232.         if ($name == 'form'{
  233.             $this->_page->acceptFormEnd();
  234.             return true;
  235.         }
  236.         if ($name == 'frameset'{
  237.             $this->_page->acceptFramesetEnd();
  238.             return true;
  239.         }
  240.         if ($this->_hasNamedTagOnOpenTagStack($name)) {
  241.             $tag array_pop($this->_tags[$name]);
  242.             if ($tag->isPrivateContent(&& $this->_private_content_tag->getTagName(== $name{
  243.                 unset($this->_private_content_tag);
  244.             }
  245.             $this->_addContentTagToOpenTags($tag);
  246.             $this->_page->acceptTag($tag);
  247.             return true;
  248.         }
  249.         return true;
  250.     }
  251.  
  252.     /**
  253.      *    Test to see if there are any open tags awaiting
  254.      *    closure that match the tag name.
  255.      *    @param string $name        Element name.
  256.      *    @return boolean            True if any are still open.
  257.      *    @access private
  258.      */
  259.     function _hasNamedTagOnOpenTagStack($name{
  260.         return isset($this->_tags[$name]&& (count($this->_tags[$name]0);
  261.     }
  262.  
  263.     /**
  264.      *    Unparsed, but relevant data. The data is added
  265.      *    to every open tag.
  266.      *    @param string $text        May include unparsed tags.
  267.      *    @return boolean            False on parse error.
  268.      *    @access public
  269.      */
  270.     function addContent($text{
  271.         if (isset($this->_private_content_tag)) {
  272.             $this->_private_content_tag->addContent($text);
  273.         else {
  274.             $this->_addContentToAllOpenTags($text);
  275.         }
  276.         return true;
  277.     }
  278.  
  279.     /**
  280.      *    Any content fills all currently open tags unless it
  281.      *    is part of an option tag.
  282.      *    @param string $text        May include unparsed tags.
  283.      *    @access private
  284.      */
  285.     function _addContentToAllOpenTags($text{
  286.         foreach (array_keys($this->_tagsas $name{
  287.             for ($i 0$count count($this->_tags[$name])$i $count$i++{
  288.                 $this->_tags[$name][$i]->addContent($text);
  289.             }
  290.         }
  291.     }
  292.  
  293.     /**
  294.      *    Parsed data in tag form. The parsed tag is added
  295.      *    to every open tag. Used for adding options to select
  296.      *    fields only.
  297.      *    @param SimpleTag $tag        Option tags only.
  298.      *    @access private
  299.      */
  300.     function _addContentTagToOpenTags(&$tag{
  301.         if ($tag->getTagName(!= 'option'{
  302.             return;
  303.         }
  304.         foreach (array_keys($this->_tagsas $name{
  305.             for ($i 0$count count($this->_tags[$name])$i $count$i++{
  306.                 $this->_tags[$name][$i]->addTag($tag);
  307.             }
  308.         }
  309.     }
  310.  
  311.     /**
  312.      *    Opens a tag for receiving content. Multiple tags
  313.      *    will be receiving input at the same time.
  314.      *    @param SimpleTag $tag        New content tag.
  315.      *    @access private
  316.      */
  317.     function _openTag(&$tag{
  318.         $name $tag->getTagName();
  319.         if (in_array($namearray_keys($this->_tags))) {
  320.             $this->_tags[$namearray();
  321.         }
  322.         $this->_tags[$name][&$tag;
  323.     }
  324. }
  325.  
  326. /**
  327.  *    A wrapper for a web page.
  328.  *    @package SimpleTest
  329.  *    @subpackage WebTester
  330.  */
  331. class SimplePage {
  332.     var $_links;
  333.     var $_title;
  334.     var $_last_widget;
  335.     var $_label;
  336.     var $_left_over_labels;
  337.     var $_open_forms;
  338.     var $_complete_forms;
  339.     var $_frameset;
  340.     var $_frames;
  341.     var $_transport_error;
  342.     var $_raw;
  343.     var $_text;
  344.     var $_sent;
  345.     var $_headers;
  346.     var $_method;
  347.     var $_url;
  348.     var $_base = false;
  349.     var $_request_data;
  350.  
  351.     /**
  352.      *    Parses a page ready to access it's contents.
  353.      *    @param SimpleHttpResponse $response     Result of HTTP fetch.
  354.      *    @access public
  355.      */
  356.     function SimplePage($response false{
  357.         $this->_links = array();
  358.         $this->_title = false;
  359.         $this->_left_over_labels = array();
  360.         $this->_open_forms = array();
  361.         $this->_complete_forms = array();
  362.         $this->_frameset = false;
  363.         $this->_frames = array();
  364.         $this->_frameset_nesting_level = 0;
  365.         $this->_text = false;
  366.         if ($response{
  367.             $this->_extractResponse($response);
  368.         else {
  369.             $this->_noResponse();
  370.         }
  371.     }
  372.  
  373.     /**
  374.      *    Extracts all of the response information.
  375.      *    @param SimpleHttpResponse $response    Response being parsed.
  376.      *    @access private
  377.      */
  378.     function _extractResponse($response{
  379.         $this->_transport_error = $response->getError();
  380.         $this->_raw = $response->getContent();
  381.         $this->_sent = $response->getSent();
  382.         $this->_headers = $response->getHeaders();
  383.         $this->_method = $response->getMethod();
  384.         $this->_url = $response->getUrl();
  385.         $this->_request_data = $response->getRequestData();
  386.     }
  387.  
  388.     /**
  389.      *    Sets up a missing response.
  390.      *    @access private
  391.      */
  392.     function _noResponse({
  393.         $this->_transport_error = 'No page fetched yet';
  394.         $this->_raw = false;
  395.         $this->_sent = false;
  396.         $this->_headers = false;
  397.         $this->_method = 'GET';
  398.         $this->_url = false;
  399.         $this->_request_data = false;
  400.     }
  401.  
  402.     /**
  403.      *    Original request as bytes sent down the wire.
  404.      *    @return mixed              Sent content.
  405.      *    @access public
  406.      */
  407.     function getRequest({
  408.         return $this->_sent;
  409.     }
  410.  
  411.     /**
  412.      *    Accessor for raw text of page.
  413.      *    @return string        Raw unparsed content.
  414.      *    @access public
  415.      */
  416.     function getRaw({
  417.         return $this->_raw;
  418.     }
  419.  
  420.     /**
  421.      *    Accessor for plain text of page as a text browser
  422.      *    would see it.
  423.      *    @return string        Plain text of page.
  424.      *    @access public
  425.      */
  426.     function getText({
  427.         if ($this->_text{
  428.             $this->_text = SimpleHtmlSaxParser::normalise($this->_raw);
  429.         }
  430.         return $this->_text;
  431.     }
  432.  
  433.     /**
  434.      *    Accessor for raw headers of page.
  435.      *    @return string       Header block as text.
  436.      *    @access public
  437.      */
  438.     function getHeaders({
  439.         if ($this->_headers{
  440.             return $this->_headers->getRaw();
  441.         }
  442.         return false;
  443.     }
  444.  
  445.     /**
  446.      *    Original request method.
  447.      *    @return string        GET, POST or HEAD.
  448.      *    @access public
  449.      */
  450.     function getMethod({
  451.         return $this->_method;
  452.     }
  453.  
  454.     /**
  455.      *    Original resource name.
  456.      *    @return SimpleUrl        Current url.
  457.      *    @access public
  458.      */
  459.     function getUrl({
  460.         return $this->_url;
  461.     }
  462.  
  463.     /**
  464.      *    Base URL if set via BASE tag page url otherwise
  465.      *    @return SimpleUrl        Base url.
  466.      *    @access public
  467.      */
  468.     function getBaseUrl({
  469.         return $this->_base;
  470.     }
  471.  
  472.     /**
  473.      *    Original request data.
  474.      *    @return mixed              Sent content.
  475.      *    @access public
  476.      */
  477.     function getRequestData({
  478.         return $this->_request_data;
  479.     }
  480.  
  481.     /**
  482.      *    Accessor for last error.
  483.      *    @return string        Error from last response.
  484.      *    @access public
  485.      */
  486.     function getTransportError({
  487.         return $this->_transport_error;
  488.     }
  489.  
  490.     /**
  491.      *    Accessor for current MIME type.
  492.      *    @return string    MIME type as string; e.g. 'text/html'
  493.      *    @access public
  494.      */
  495.     function getMimeType({
  496.         if ($this->_headers{
  497.             return $this->_headers->getMimeType();
  498.         }
  499.         return false;
  500.     }
  501.  
  502.     /**
  503.      *    Accessor for HTTP response code.
  504.      *    @return integer    HTTP response code received.
  505.      *    @access public
  506.      */
  507.     function getResponseCode({
  508.         if ($this->_headers{
  509.             return $this->_headers->getResponseCode();
  510.         }
  511.         return false;
  512.     }
  513.  
  514.     /**
  515.