Subversion Repositories PHPX

Rev

Rev 19 | Rev 21 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 PointedEar 1
<?php
2
 
3
require_once 'global.inc';
4
 
20 PointedEar 5
$encoding = mb_detect_encoding(file_get_contents($_SERVER['SCRIPT_FILENAME']));
6
if ($encoding === 'ASCII') $encoding = 'iso-8859-1';
7
define('FEATURES_ENCODING', $encoding);
13 PointedEar 8
 
2 PointedEar 9
/**
10
 * A list of language features with URNs definitions
11
 * for reference links.
12
 */
13
class FeatureList
14
{
15
  public $versions = array();
16
 
17
  /**
18
   * Versions of implementations that are considered safe.
19
   * A feature is considered safe if it does not require
20
   * an implementation version above these versions.
21
   *
22
   * @var Array[string=>string]
23
   */
24
  public $safeVersions = array();
25
 
26
  /**
27
   * URNs that can be used for reference links.
28
   *
29
   * @var Array[string=>string]
30
   */
31
  protected $urns = array();
32
 
33
  /**
19 PointedEar 34
   * <code>true</code> generates form controls for submitting test case
35
   * results
36
   *
37
   * @var bool
38
   */
39
  protected $testcase = false;
40
 
41
  /**
2 PointedEar 42
   * The list of language features
43
   *
44
   * @var array[Features]
45
   */
46
  protected $items = array();
47
 
48
  /**
49
   * Determines the number of printed items the table headings should be repeated
50
   *
51
   * @var int
52
   */
53
  protected $headerRepeat = 25;
54
 
55
  /**
56
   * Initializes the FeatureList object
57
   *
58
   * @param array|Object $a
59
   * @return FeatureList
60
   */
61
  public function __construct($a)
62
  {
9 PointedEar 63
    $aVars = get_class_vars(get_class($this));
64
 
19 PointedEar 65
 
66
    foreach ($aVars as $key => $value)
2 PointedEar 67
    {
68
      if (isset($a[$key]))
69
      {
70
        $this->$key = $a[$key];
71
      }
72
    }
19 PointedEar 73
 
2 PointedEar 74
    /* Inform items of ourself so that URNs can be used for links */
75
    if (is_array($this->items))
76
    {
77
      foreach ($this->items as &$item)
78
      {
4 PointedEar 79
        $item->setList($this);
2 PointedEar 80
      }
81
    }
82
 
83
    /* resolve URN references that are URNs */
84
    if (is_array($this->urns))
85
    {
86
      foreach ($this->urns as &$urn)
87
      {
88
        if (($url = $this->resolveURN($urn)))
89
        {
90
          $urn = $url;
91
        }
92
      }
93
    }
94
  }
95
 
19 PointedEar 96
  /*
97
   * Protected properties may be read, but not written
98
   */
99
  public function __get($property)
100
  {
101
    if (property_exists(get_class($this), $property))
102
    {
103
      return $this->$property;
104
    }
105
  }
106
 
2 PointedEar 107
  public function printHeaders()
108
  {
19 PointedEar 109
    foreach ($this->versions as $key => $ver)
2 PointedEar 110
    {
19 PointedEar 111
      if ($key || $this->testcase)
112
      {
2 PointedEar 113
?>
114
          <th><?php echo $ver; ?></th>
115
<?php
19 PointedEar 116
      }
2 PointedEar 117
    }
118
  }
119
 
120
  /**
121
   * Prints the list of features.
122
   *
123
   * @see Feature::printMe()
124
   */
125
  public function printItems()
126
  {
127
    $counter = 0;
128
    $headerRepeat = $this->headerRepeat;
129
    $repeatHeaders = ($headerRepeat > 1);
130
 
131
    foreach ($this->items as $feature)
132
    {
133
      if ($feature instanceof Feature)
134
      {
16 PointedEar 135
        /*
136
         * TODO: Disabled header repetition until footnote ref. name/ID
137
         * problem has been solved
138
         */
139
//        if ($repeatHeaders
140
//            && $counter > 1
141
//            && $counter % $headerRepeat === 0)
142
//        {
143
//          echo <<<HTML
144
//        <tr class="header">
145
//          <th>Feature</th>
146
//          {$this->printHeaders()}
147
//        </tr>
148
//HTML;
149
//        }
2 PointedEar 150
 
151
        $feature->printMe();
152
 
153
        $counter++;
154
      }
155
    }
156
  }
157
 
158
  /**
159
   * Resolves a URN according to the value of the
160
   * object's <code>$urn</code> property.
161
   *
162
   * @param string $urn
163
   *   URN to be resolved
164
   * @return string|boolean
165
   *   The resolved URN if successful,
166
   *   <code>false</code> otherwise.
167
   */
168
  public function resolveURN($urn)
169
  {
170
    if (is_array($this->urns))
171
    {
172
      $reURN = '|^(.+?):(?!//)|';
173
 
174
      if (preg_match($reURN, $urn, $m) && isset($this->urns[$m[1]]))
175
      {
176
        return preg_replace($reURN, $this->urns[$m[1]], $urn);
177
      }
178
    }
179
 
180
    return $urn;
181
  }
182
}
183
 
184
/**
185
 * A language feature.
186
 */
187
class Feature
188
{
189
  /**
190
   * Fragment identifiers to be defined for quickly accessing
191
   * the feature description.
192
   *
193
   * @var Array[String]
194
   */
195
  protected $anchors = array();
196
 
197
  /**
198
   * Value of the explanatory <code>title</code> attribute for the feature.
199
   *
200
   * @var string
201
   */
202
  protected $title = '';
203
 
204
  /**
205
   * Name or example code of the feature
206
   *
207
   * @var string
208
   */
209
  protected $content = '';
210
 
211
  /**
212
   * Description of the feature.  Displayed directly if code is missing,
213
   * otherwise used as `title' attribute value.
214
   *
215
   * @var string
216
   */
217
  protected $descr = '';
218
 
219
  /**
220
   * Versions that support this feature
221
   *
222
   * @var Array
223
   */
224
  protected $versions = array();
225
 
226
  /**
227
   * Reference to the FeatureList that this feature belongs to
228
   *
229
   * @var FeatureList
230
   */
231
  protected $list = null;
232
 
4 PointedEar 233
  public function setList(&$oList)
2 PointedEar 234
  {
235
    $this->list =& $oList;
236
  }
237
 
238
  /**
239
   * Creates a new Feature object, using values from the passed parameters
240
   * array.
241
   *
242
   * @param array|Object $params
243
   * @return Feature
244
   */
245
  public function __construct($params = array())
246
  {
14 PointedEar 247
    $aVars = get_class_vars(__CLASS__);
9 PointedEar 248
 
19 PointedEar 249
    foreach ($aVars as $key => $value)
2 PointedEar 250
    {
9 PointedEar 251
      if (isset($params[$key]))
252
      {
253
        $this->$key = $params[$key];
254
      }
2 PointedEar 255
    }
256
  }
257
 
258
  /**
17 PointedEar 259
   * Determines whether one version is greater than another.
260
   *
261
   * @param string $v1  Version string #1
262
   * @param string $v2  Version string #2
263
   * @return bool
264
   *   <code>true</code> if the version <var>$v1</var> is greater than
265
   *   the version <var>$v2</var>, <code>false</code> otherwise
266
   */
267
  protected static function _versionIsGreater($v1, $v2)
268
  {
269
    $v1 = explode('.', $v1);
270
    $v2 = explode('.', $v2);
271
 
272
    foreach ($v1 as $key => $value)
273
    {
274
      if ((int)$value <= (int)$v2[$key])
275
      {
276
        return false;
277
      }
278
    }
279
 
280
    return true;
281
  }
282
 
283
  /**
2 PointedEar 284
   * Returns <code>' class="safe"'</code> if the feature
285
   * can be considered safe.  The required information
286
   * is stored in the <code>safeVersions</code> property
287
   * of the associated <code>FeatureList</code> object.
288
   *
289
   * @return string
290
   * @see FeatureList::defaultSafeVersions
291
   */
292
  protected function getSafeStr()
293
  {
294
    if (!is_null($this->list))
295
    {
296
      foreach ($this->list->safeVersions as $impl => &$safeVer)
297
      {
298
        $thisImplVer =& $this->versions[$impl];
299
        if (is_array($thisImplVer))
300
        {
301
          if (isset($thisImplVer['tested']) && !is_bool($thisImplVer['tested']))
302
          {
303
            $thisImplVer =& $thisImplVer['tested'];
304
          }
305
          else
306
        {
307
            $thisImplVer =& $thisImplVer[0];
308
          }
309
        }
310
 
311
        /* DEBUG */
312
        // echo " $impl=$thisImplVer ";
313
 
17 PointedEar 314
        if (preg_match('/^-?$/', $thisImplVer) || self::_versionIsGreater($thisImplVer, $safeVer))
2 PointedEar 315
        {
316
          return '';
317
        }
318
      }
319
 
320
      return ' class="safe"';
321
    }
322
    else
323
    {
324
      return '';
325
    }
326
  }
327
 
328
  protected function getTitleStr()
329
  {
330
    if (!empty($this->title))
331
    {
332
      return " title=\"{$this->title}\"";
333
    }
334
    else
335
    {
336
      return '';
337
    }
338
  }
339
 
340
  protected function getAnchors()
341
  {
342
    $result = array();
343
 
344
    foreach ($this->anchors as $anchor)
345
    {
346
      $result[] = "<a name=\"{$anchor}\"";
347
 
348
      if (preg_match('/^[a-z][a-z0-9_:.-]*/i', $anchor))
349
      {
350
        $result[] = " id=\"{$anchor}\"";
351
      }
352
 
353
      $result[] = '></a>';
354
    }
355
 
356
    return join('', $result);
357
  }
358
 
359
  protected function getAssumed($v)
360
  {
361
    if (is_array($v) && isset($v['assumed']) && $v['assumed'])
362
    {
363
      return ' class="assumed"';
364
    }
365
 
366
    return '';
367
  }
368
 
369
  protected function getTested($v)
370
  {
371
    if (is_array($v) && isset($v['tested']) && $v['tested'])
372
    {
373
      return ' class="tested"';
374
    }
375
 
376
    return '';
377
  }
378
 
379
  /**
380
   * Returns the version of a feature.
381
   *
382
   * @param string|VersionInfo $vInfo
383
   * @return mixed
384
   */
385
  protected function getVer($vInfo)
386
  {
387
    if (is_array($vInfo))
388
    {
389
      /* TODO: Return all versions: documented, assumed, and tested */
390
      $vNumber = (isset($vInfo['tested'])
391
                  && gettype($vInfo['tested']) !== 'boolean')
392
                     ? $vInfo['tested']
393
                     : $vInfo[0];
394
      $section = isset($vInfo['section'])
395
                   ? ' <span class="section" title="Specification section">['
396
                      . $vInfo['section'] . ']</span>'
397
                   : '';
398
 
399
      if (isset($vInfo['urn']))
400
      {
401
        if ($this->list instanceof FeatureList)
402
        {
403
          $url = $this->list->resolveURN($vInfo['urn']);
404
          $vNumber = '<a href="' . $url . '">' . $vNumber
6 PointedEar 405
            . ($section ? $section : '') . '</a>';
2 PointedEar 406
        }
407
      }
408
      else if ($section)
409
      {
410
        $vNumber .= $section;
411
      }
412
 
6 PointedEar 413
      $vInfo = $vNumber;
2 PointedEar 414
    }
6 PointedEar 415
 
416
    return ($vInfo === '-')
417
      ? '<span title="Not supported">&#8722;</span>'
418
      : $vInfo;
2 PointedEar 419
  }
17 PointedEar 420
 
421
  /**
422
   * Returns a syntax-highlighted version of a string
423
   *
424
   * @param string $s
425
   * @return string
426
   */
2 PointedEar 427
  protected static function shl($s)
428
  {
429
    /* stub */
17 PointedEar 430
    return $s;
2 PointedEar 431
  }
432
 
433
  public function printMe()
434
  {
435
    ?>
436
<tr<?php echo $this->getSafeStr(); ?>>
437
          <th<?php echo $this->getTitleStr(); ?>><?php
438
            echo $this->getAnchors();
439
            echo /*preg_replace_callback(
440
              '#(<code>)(.+?)(</code>)#',
441
              array('self', 'shl'),*/
442
              preg_replace('/&hellip;/', '&#8230;', $this->content)/*)*/;
443
            ?></th>
444
<?php
445
    $versions = $this->versions;
19 PointedEar 446
    $testcase = false;
2 PointedEar 447
    if (!is_null($this->list))
448
    {
449
      $versions =& $this->list->versions;
19 PointedEar 450
      $testcase = $this->list->testcase;
2 PointedEar 451
    }
452
 
453
    static $row = 0;
454
    $row++;
455
 
456
    $column = 0;
17 PointedEar 457
    $thisVersions =& $this->versions;
2 PointedEar 458
 
459
    foreach ($versions as $key => $value)
460
    {
461
      $column++;
462
      $id = "td$row-$column";
17 PointedEar 463
      $ver = isset($thisVersions[$key]) ? $thisVersions[$key] : '';
19 PointedEar 464
      if ($key || $testcase)
465
      {
2 PointedEar 466
?>
17 PointedEar 467
          <td<?php
19 PointedEar 468
            if (!$key)
469
            {
470
              echo " id='$id'";
471
            }
472
 
2 PointedEar 473
            echo $this->getAssumed($ver) . $this->getTested($ver);
17 PointedEar 474
 
2 PointedEar 475
            if (!$key)
476
            {
477
              if (!empty($ver))
478
              {
13 PointedEar 479
                echo ' title="Test code: '
480
                  . htmlspecialchars(
17 PointedEar 481
                      preg_replace('/\\\(["\'])/', '\1',
482
                        reduceWhitespace($ver)
483
                      ),
13 PointedEar 484
                      ENT_COMPAT,
17 PointedEar 485
                      FEATURES_ENCODING
486
                    )
13 PointedEar 487
                  . '"';
2 PointedEar 488
              }
489
              else
19 PointedEar 490
            {
2 PointedEar 491
                echo ' title="Not applicable: No automated test case'
17 PointedEar 492
                  . ' is available for this feature.  If possible, please'
493
                  . ' click the feature code in the first column to run'
2 PointedEar 494
                  . ' a manual test."';
495
              }
496
            }
19 PointedEar 497
            else
498
          {
499
              echo ' title="'
500
                . htmlspecialchars(
501
                    preg_replace('/<.*?>/', '', $value),
502
                    ENT_COMPAT, FEATURES_ENCODING)
503
                . '"';
504
            }
2 PointedEar 505
            ?>><?php
506
            if ($key)
507
            {
508
              echo $this->getVer($ver);
17 PointedEar 509
 
510
              /* General footnotes support: include footnotes.class.php to enable */
511
              if (is_array($ver) && isset($ver['footnote']) && $ver['footnote'])
512
              {
513
                echo $ver['footnote'];
514
              }
2 PointedEar 515
            }
516
            else
19 PointedEar 517
          {
518
              if (!empty($ver) && $testcase)
2 PointedEar 519
              {
520
                ?><script type="text/javascript">
521
  // <![CDATA[
19 PointedEar 522
  var s = test(<?php echo $ver; ?>,
523
    '<input title="Supported" name="<?php echo htmlspecialchars($this->title, ENT_COMPAT, FEATURES_ENCODING); ?>" value="+" readonly>',
524
    '<input title="Not supported" name="<?php echo htmlspecialchars($this->title, ENT_COMPAT, FEATURES_ENCODING); ?>" value="&#8722;" readonly>');
525
  jsx.tryThis("document.write(s);",
2 PointedEar 526
          "document.getElementById('<?php echo $id; ?>').appendChild("
527
          + "document.createTextNode(s));");
528
  // ]]>
529
</script><?php
530
              }
531
              else
19 PointedEar 532
            {
2 PointedEar 533
                echo '<abbr>N/A</abbr>';
534
              }
535
            }
536
            ?></td>
537
<?php
19 PointedEar 538
      }
2 PointedEar 539
    }
540
?>
541
        </tr>
542
<?php
543
  }
544
}
545
 
546
?>