Subversion Repositories PHPX

Rev

Rev 28 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 PointedEar 1
<?php
2
 
28 PointedEar 3
require_once __DIR__ . '/global.inc';
2 PointedEar 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.
21 PointedEar 186
 *
187
 * @property-read Array[String] $anchors
188
 *   Fragment identifiers to be defined for quickly accessing
189
 *   the feature description.
190
 * @property-read string $title
191
 *   Value of the explanatory <code>title</code> attribute for the feature.
192
 * @property-read string $content
193
 *   Name or example code of the feature
194
 * @property-read string $descr
195
 *   Description of the feature.  Displayed directly if code is missing,
196
 *   otherwise used as `title' attribute value.
197
 * @property-read Array $versions
198
 *   Versions that support this feature
199
 * @property-read List $list
200
 *   Reference to the FeatureList that this feature belongs to
2 PointedEar 201
 */
202
class Feature
203
{
204
  /**
205
   * Fragment identifiers to be defined for quickly accessing
206
   * the feature description.
207
   *
208
   * @var Array[String]
209
   */
210
  protected $anchors = array();
211
 
212
  /**
213
   * Value of the explanatory <code>title</code> attribute for the feature.
214
   *
215
   * @var string
216
   */
217
  protected $title = '';
218
 
219
  /**
220
   * Name or example code of the feature
221
   *
222
   * @var string
223
   */
224
  protected $content = '';
225
 
226
  /**
227
   * Description of the feature.  Displayed directly if code is missing,
228
   * otherwise used as `title' attribute value.
229
   *
230
   * @var string
231
   */
232
  protected $descr = '';
233
 
234
  /**
235
   * Versions that support this feature
236
   *
237
   * @var Array
238
   */
239
  protected $versions = array();
240
 
241
  /**
242
   * Reference to the FeatureList that this feature belongs to
243
   *
244
   * @var FeatureList
245
   */
246
  protected $list = null;
247
 
4 PointedEar 248
  public function setList(&$oList)
2 PointedEar 249
  {
250
    $this->list =& $oList;
251
  }
252
 
253
  /**
254
   * Creates a new Feature object, using values from the passed parameters
255
   * array.
256
   *
257
   * @param array|Object $params
258
   * @return Feature
259
   */
260
  public function __construct($params = array())
261
  {
14 PointedEar 262
    $aVars = get_class_vars(__CLASS__);
9 PointedEar 263
 
19 PointedEar 264
    foreach ($aVars as $key => $value)
2 PointedEar 265
    {
9 PointedEar 266
      if (isset($params[$key]))
267
      {
268
        $this->$key = $params[$key];
269
      }
2 PointedEar 270
    }
271
  }
21 PointedEar 272
 
273
  /*
274
  * Protected properties may be read, but not written
275
  */
276
  public function __get($property)
277
  {
278
    if (property_exists(get_class($this), $property))
279
    {
280
      return $this->$property;
281
    }
282
  }
2 PointedEar 283
 
284
  /**
17 PointedEar 285
   * Determines whether one version is greater than another.
286
   *
287
   * @param string $v1  Version string #1
288
   * @param string $v2  Version string #2
289
   * @return bool
290
   *   <code>true</code> if the version <var>$v1</var> is greater than
291
   *   the version <var>$v2</var>, <code>false</code> otherwise
292
   */
293
  protected static function _versionIsGreater($v1, $v2)
294
  {
295
    $v1 = explode('.', $v1);
296
    $v2 = explode('.', $v2);
297
 
298
    foreach ($v1 as $key => $value)
299
    {
300
      if ((int)$value <= (int)$v2[$key])
301
      {
302
        return false;
303
      }
304
    }
305
 
306
    return true;
307
  }
308
 
309
  /**
2 PointedEar 310
   * Returns <code>' class="safe"'</code> if the feature
311
   * can be considered safe.  The required information
312
   * is stored in the <code>safeVersions</code> property
313
   * of the associated <code>FeatureList</code> object.
314
   *
315
   * @return string
316
   * @see FeatureList::defaultSafeVersions
317
   */
318
  protected function getSafeStr()
319
  {
320
    if (!is_null($this->list))
321
    {
322
      foreach ($this->list->safeVersions as $impl => &$safeVer)
323
      {
324
        $thisImplVer =& $this->versions[$impl];
325
        if (is_array($thisImplVer))
326
        {
327
          if (isset($thisImplVer['tested']) && !is_bool($thisImplVer['tested']))
328
          {
329
            $thisImplVer =& $thisImplVer['tested'];
330
          }
331
          else
332
        {
333
            $thisImplVer =& $thisImplVer[0];
334
          }
335
        }
336
 
337
        /* DEBUG */
338
        // echo " $impl=$thisImplVer ";
339
 
17 PointedEar 340
        if (preg_match('/^-?$/', $thisImplVer) || self::_versionIsGreater($thisImplVer, $safeVer))
2 PointedEar 341
        {
342
          return '';
343
        }
344
      }
345
 
346
      return ' class="safe"';
347
    }
348
    else
349
    {
350
      return '';
351
    }
352
  }
353
 
354
  protected function getTitleStr()
355
  {
356
    if (!empty($this->title))
357
    {
358
      return " title=\"{$this->title}\"";
359
    }
360
    else
361
    {
362
      return '';
363
    }
364
  }
365
 
366
  protected function getAnchors()
367
  {
368
    $result = array();
369
 
370
    foreach ($this->anchors as $anchor)
371
    {
372
      $result[] = "<a name=\"{$anchor}\"";
373
 
374
      if (preg_match('/^[a-z][a-z0-9_:.-]*/i', $anchor))
375
      {
376
        $result[] = " id=\"{$anchor}\"";
377
      }
378
 
379
      $result[] = '></a>';
380
    }
381
 
382
    return join('', $result);
383
  }
384
 
385
  protected function getAssumed($v)
386
  {
387
    if (is_array($v) && isset($v['assumed']) && $v['assumed'])
388
    {
389
      return ' class="assumed"';
390
    }
391
 
392
    return '';
393
  }
394
 
395
  protected function getTested($v)
396
  {
397
    if (is_array($v) && isset($v['tested']) && $v['tested'])
398
    {
399
      return ' class="tested"';
400
    }
401
 
402
    return '';
403
  }
404
 
405
  /**
406
   * Returns the version of a feature.
407
   *
408
   * @param string|VersionInfo $vInfo
409
   * @return mixed
410
   */
411
  protected function getVer($vInfo)
412
  {
413
    if (is_array($vInfo))
414
    {
415
      /* TODO: Return all versions: documented, assumed, and tested */
416
      $vNumber = (isset($vInfo['tested'])
417
                  && gettype($vInfo['tested']) !== 'boolean')
418
                     ? $vInfo['tested']
419
                     : $vInfo[0];
420
      $section = isset($vInfo['section'])
421
                   ? ' <span class="section" title="Specification section">['
422
                      . $vInfo['section'] . ']</span>'
423
                   : '';
424
 
425
      if (isset($vInfo['urn']))
426
      {
427
        if ($this->list instanceof FeatureList)
428
        {
429
          $url = $this->list->resolveURN($vInfo['urn']);
430
          $vNumber = '<a href="' . $url . '">' . $vNumber
6 PointedEar 431
            . ($section ? $section : '') . '</a>';
2 PointedEar 432
        }
433
      }
434
      else if ($section)
435
      {
436
        $vNumber .= $section;
437
      }
438
 
6 PointedEar 439
      $vInfo = $vNumber;
2 PointedEar 440
    }
6 PointedEar 441
 
442
    return ($vInfo === '-')
443
      ? '<span title="Not supported">&#8722;</span>'
444
      : $vInfo;
2 PointedEar 445
  }
17 PointedEar 446
 
447
  /**
448
   * Returns a syntax-highlighted version of a string
449
   *
450
   * @param string $s
451
   * @return string
452
   */
2 PointedEar 453
  protected static function shl($s)
454
  {
455
    /* stub */
17 PointedEar 456
    return $s;
2 PointedEar 457
  }
458
 
459
  public function printMe()
460
  {
461
    ?>
462
<tr<?php echo $this->getSafeStr(); ?>>
463
          <th<?php echo $this->getTitleStr(); ?>><?php
464
            echo $this->getAnchors();
465
            echo /*preg_replace_callback(
466
              '#(<code>)(.+?)(</code>)#',
467
              array('self', 'shl'),*/
468
              preg_replace('/&hellip;/', '&#8230;', $this->content)/*)*/;
469
            ?></th>
470
<?php
471
    $versions = $this->versions;
19 PointedEar 472
    $testcase = false;
2 PointedEar 473
    if (!is_null($this->list))
474
    {
475
      $versions =& $this->list->versions;
19 PointedEar 476
      $testcase = $this->list->testcase;
2 PointedEar 477
    }
478
 
479
    static $row = 0;
480
    $row++;
481
 
482
    $column = 0;
17 PointedEar 483
    $thisVersions =& $this->versions;
2 PointedEar 484
 
485
    foreach ($versions as $key => $value)
486
    {
487
      $column++;
488
      $id = "td$row-$column";
17 PointedEar 489
      $ver = isset($thisVersions[$key]) ? $thisVersions[$key] : '';
19 PointedEar 490
      if ($key || $testcase)
491
      {
2 PointedEar 492
?>
17 PointedEar 493
          <td<?php
19 PointedEar 494
            if (!$key)
495
            {
496
              echo " id='$id'";
497
            }
498
 
2 PointedEar 499
            echo $this->getAssumed($ver) . $this->getTested($ver);
17 PointedEar 500
 
2 PointedEar 501
            if (!$key)
502
            {
503
              if (!empty($ver))
504
              {
13 PointedEar 505
                echo ' title="Test code: '
506
                  . htmlspecialchars(
17 PointedEar 507
                      preg_replace('/\\\(["\'])/', '\1',
508
                        reduceWhitespace($ver)
509
                      ),
13 PointedEar 510
                      ENT_COMPAT,
17 PointedEar 511
                      FEATURES_ENCODING
512
                    )
13 PointedEar 513
                  . '"';
2 PointedEar 514
              }
515
              else
19 PointedEar 516
            {
2 PointedEar 517
                echo ' title="Not applicable: No automated test case'
17 PointedEar 518
                  . ' is available for this feature.  If possible, please'
519
                  . ' click the feature code in the first column to run'
2 PointedEar 520
                  . ' a manual test."';
521
              }
522
            }
19 PointedEar 523
            else
524
          {
525
              echo ' title="'
526
                . htmlspecialchars(
527
                    preg_replace('/<.*?>/', '', $value),
528
                    ENT_COMPAT, FEATURES_ENCODING)
529
                . '"';
530
            }
2 PointedEar 531
            ?>><?php
532
            if ($key)
533
            {
534
              echo $this->getVer($ver);
17 PointedEar 535
 
536
              /* General footnotes support: include footnotes.class.php to enable */
537
              if (is_array($ver) && isset($ver['footnote']) && $ver['footnote'])
538
              {
539
                echo $ver['footnote'];
540
              }
2 PointedEar 541
            }
542
            else
19 PointedEar 543
          {
544
              if (!empty($ver) && $testcase)
2 PointedEar 545
              {
546
                ?><script type="text/javascript">
547
  // <![CDATA[
36 PointedEar 548
  var s = test(<?php echo $ver; ?>, '<span title="Supported">+<\/span>',
549
    '<span title="Not supported">&#8722;<\/span>');
19 PointedEar 550
  jsx.tryThis("document.write(s);",
2 PointedEar 551
          "document.getElementById('<?php echo $id; ?>').appendChild("
552
          + "document.createTextNode(s));");
553
  // ]]>
554
</script><?php
555
              }
556
              else
19 PointedEar 557
            {
2 PointedEar 558
                echo '<abbr>N/A</abbr>';
559
              }
560
            }
561
            ?></td>
562
<?php
19 PointedEar 563
      }
2 PointedEar 564
    }
565
?>
566
        </tr>
567
<?php
568
  }
569
}
570
 
571
?>