<?php

/** @file

 * This extension provides support for common HTML list operations.

 */



/**

 * Provide list operations for QueryPath.

 *

 * The QPList class is an extension to QueryPath. It provides HTML list generators

 * that take lists and convert them into bulleted lists inside of QueryPath.

 *

 * @deprecated This will be removed from a subsequent version of QueryPath. It will

 *  be released as a stand-alone extension.

 * @ingroup querypath_extensions

 */ 

class QPList implements QueryPathExtension {

  const UL = 'ul';

  const OL = 'ol';

  const DL = 'dl';

  

  protected $qp = NULL;

  public function __construct(QueryPath $qp) {

    $this->qp = $qp;

  }

  

  public function appendTable($items, $options = array()) {

    $opts = $options + array(

      'table class' => 'qptable',

    );

    $base = '<?xml version="1.0"?>

    <table>

    <tbody>

      <tr></tr>

    </tbody>

    </table>';

    

    $qp = qp($base, 'table')->addClass($opts['table class'])->find('tr');

    if ($items instanceof TableAble) {

      $headers = $items->getHeaders();

      $rows = $items->getRows();

    }

    elseif ($items instanceof Traversable) {

      $headers = array();

      $rows = $items;

    }

    else {

      $headers = $items['headers'];

      $rows = $items['rows'];

    }

    

    // Add Headers:

    foreach ($headers as $header) {

      $qp->append('<th>' . $header . '</th>');

    }

    $qp->top()->find('tr:last');

    

    // Add rows and cells.

    foreach ($rows as $row) {

      $qp->after('<tr/>')->next();

      foreach($row as $cell) $qp->append('<td>' . $cell . '</td>');

    }

    

    $this->qp->append($qp->top());

    

    return $this->qp;

  }

  

  /**

   * Append a list of items into an HTML DOM using one of the HTML list structures.

   * This takes a one-dimensional array and converts it into an HTML UL or OL list,

   * <b>or</b> it can take an associative array and convert that into a DL list.

   *

   * In addition to arrays, this works with any Traversable or Iterator object.

   *

   * OL/UL arrays can be nested.

   *

   * @param mixed $items

   *   An indexed array for UL and OL, or an associative array for DL. Iterator and

   *  Traversable objects can also be used.

   * @param string $type

   *  One of ul, ol, or dl. Predefined constants are available for use.

   * @param array $options

   *  An associative array of configuration options. The supported options are:

   *  - 'list class': The class that will be assigned to a list.

   */

  public function appendList($items, $type = self::UL, $options = array()) {

    $opts = $options + array(

      'list class' => 'qplist',

    );

    if ($type == self::DL) {

      $q = qp('<?xml version="1.0"?><dl></dl>', 'dl')->addClass($opts['list class']);

      foreach ($items as $dt => $dd) {

        $q->append('<dt>' . $dt . '</dt><dd>' . $dd . '</dd>');

      }

      $q->appendTo($this->qp);

    }

    else {

      $q = $this->listImpl($items, $type, $opts);

      $this->qp->append($q->find(':root'));

    }

    

    return $this->qp;

  }

  

  /**

   * Internal recursive list generator for appendList.

   */

  protected function listImpl($items, $type, $opts, $q = NULL) {

    $ele = '<' . $type . '/>';

    if (!isset($q))

      $q = qp()->append($ele)->addClass($opts['list class']);

          

    foreach ($items as $li) {

      if ($li instanceof QueryPath) {

        $q = $this->listImpl($li->get(), $type, $opts, $q);

      }

      elseif (is_array($li) || $li instanceof Traversable) {

        $q->append('<li><ul/></li>')->find('li:last > ul');

        $q = $this->listImpl($li, $type, $opts, $q);

        $q->parent();

      }

      else {

        $q->append('<li>' . $li . '</li>');

      }

    }

    return $q;

  }

  

  /**

   * Unused.

   */

  protected function isAssoc($array) {

    // A clever method from comment on is_array() doc page:

    return count(array_diff_key($array, range(0, count($array) - 1))) != 0; 

  }

}

QueryPathExtensionRegistry::extend('QPList');



/**

 * A TableAble object represents tabular data and can be converted to a table.

 *

 * The {@link QPList} extension to {@link QueryPath} provides a method for

 * appending a table to a DOM ({@link QPList::appendTable()}).

 *

 * Implementing classes should provide methods for getting headers, rows

 * of data, and the number of rows in the table ({@link TableAble::size()}).

 * Implementors may also choose to make classes Iterable or Traversable over

 * the rows of the table.

 *

 * Two very basic implementations of TableAble are provided in this package:

 *  - {@link QPTableData} provides a generic implementation.

 *  - {@link QPTableTextData} provides a generic implementation that also escapes

 *    all data.

 */

interface TableAble {

  public function getHeaders();

  public function getRows();

  public function size();

}



/**

 * Format data to be inserted into a simple HTML table.

 *

 * Data in the headers or rows may contain markup. If you want to 

 * disallow markup, use a {@see QPTableTextData} object instead.

 */

class QPTableData implements TableAble, IteratorAggregate {

  

  protected $headers;

  protected $rows;

  protected $caption;

  protected $p = -1;

  

  public function setHeaders($array) {$this->headers = $array; return $this;}

  public function getHeaders() {return $this->headers; }

  public function setRows($array) {$this->rows = $array; return $this;}

  public function getRows() {return $this->rows;}

  public function size() {return count($this->rows);}

  public function getIterator() {

    return new ArrayIterator($rows);

  }

}



/**

 * Provides a table where all of the headers and data are treated as text data.

 * 

 * This provents marked-up data from being inserted into the DOM as elements. 

 * Instead, the text is escaped using {@see htmlentities()}.

 *

 * @see QPTableData

 */

class QPTableTextData extends QPTableData {

  public function setHeaders($array) {

    $headers = array();

    foreach ($array as $header) {

      $headers[] = htmlentities($header);

    }

    parent::setHeaders($headers);

    return $this;

  }

  public function setRows($array) {

    $count = count($array);

    for ($i = 0; $i < $count; ++$i) {

      $cols = array();

      foreach ($data[$i] as $datum) {

        $cols[] = htmlentities($datum);

      }

      $data[$i] = $cols;

    }

    parent::setRows($array);

    return $this;

  }

}