summaryrefslogtreecommitdiff
path: root/lib/querypath/Extension/QPList.php
blob: 87fb860a3232420905bd80129342581b91847f0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<?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;
  }
}