Subversion Repositories PHPX

Rev

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

1
<?php

require_once __DIR__ . '/global.inc';

$encoding = mb_detect_encoding(file_get_contents($_SERVER['SCRIPT_FILENAME']));
if ($encoding === 'ASCII') $encoding = 'iso-8859-1';
define('FEATURES_ENCODING', $encoding);

/**
 * A list of language features with URNs definitions
 * for reference links.
 */

class FeatureList
{
  public $versions = array();
 
  /**
   * Versions of implementations that are considered safe.
   * A feature is considered safe if it does not require
   * an implementation version above these versions.
   *
   * @var Array[string=>string]
   */

  public $safeVersions = array();
 
  /**
   * URNs that can be used for reference links.
   *
   * @var Array[string=>string]
   */

  protected $urns = array();
 
  /**
   * <code>true</code> generates form controls for submitting test case
   * results
   *
   * @var bool
   */

  protected $testcase = false;
 
  /**
   * The list of language features
   *
   * @var array[Features]
   */

  protected $items = array();
 
  /**
   * Determines the number of printed items the table headings should be repeated
   *
   * @var int
   */

  protected $headerRepeat = 25;

  /**
   * Initializes the FeatureList object
   *
   * @param array|Object $a
   * @return FeatureList
   */

  public function __construct($a)
  {
    $aVars = get_class_vars(get_class($this));
   

    foreach ($aVars as $key => $value)
    {
      if (isset($a[$key]))
      {
        $this->$key = $a[$key];
      }
    }

    /* Inform items of ourself so that URNs can be used for links */
    if (is_array($this->items))
    {
      foreach ($this->items as &$item)
      {
        $item->setList($this);
      }
    }
   
    /* resolve URN references that are URNs */
    if (is_array($this->urns))
    {
      foreach ($this->urns as &$urn)
      {
        if (($url = $this->resolveURN($urn)))
        {
          $urn = $url;
        }
      }
    }
  }
 
  /*
   * Protected properties may be read, but not written
   */

  public function __get($property)
  {
    if (property_exists(get_class($this), $property))
    {
      return $this->$property;
    }
  }
 
  public function printHeaders()
  {
    foreach ($this->versions as $key => $ver)
    {
      if ($key || $this->testcase)
      {
?>
          <th><?php echo $ver; ?></th>
<?php
      }
    }
  }
   
  /**
   * Prints the list of features.
   *
   * @see Feature::printMe()
   */

  public function printItems()
  {
    $counter = 0;
    $headerRepeat = $this->headerRepeat;
    $repeatHeaders = ($headerRepeat > 1);
   
    foreach ($this->items as $feature)
    {
      if ($feature instanceof Feature)
      {
        /*
         * TODO: Disabled header repetition until footnote ref. name/ID
         * problem has been solved
         */

//        if ($repeatHeaders
//            && $counter > 1
//            && $counter % $headerRepeat === 0)
//        {
//          echo <<<HTML
//        <tr class="header">
//          <th>Feature</th>
//          {$this->printHeaders()}
//        </tr>
//HTML;
//        }
       
        $feature->printMe();
       
        $counter++;
      }
    }
  }
 
  /**
   * Resolves a URN according to the value of the
   * object's <code>$urn</code> property.
   *
   * @param string $urn
   *   URN to be resolved
   * @return string|boolean
   *   The resolved URN if successful,
   *   <code>false</code> otherwise.
   */

  public function resolveURN($urn)
  {
    if (is_array($this->urns))
    {
      $reURN = '|^(.+?):(?!//)|';
     
      if (preg_match($reURN, $urn, $m) && isset($this->urns[$m[1]]))
      {
        return preg_replace($reURN, $this->urns[$m[1]], $urn);
      }
    }

    return $urn;
  }
}

/**
 * A language feature.
 *
 * @property-read Array[String] $anchors
 *   Fragment identifiers to be defined for quickly accessing
 *   the feature description.
 * @property-read string $title
 *   Value of the explanatory <code>title</code> attribute for the feature.
 * @property-read string $content
 *   Name or example code of the feature
 * @property-read string $descr
 *   Description of the feature.  Displayed directly if code is missing,
 *   otherwise used as `title' attribute value.
 * @property-read Array $versions
 *   Versions that support this feature
 * @property-read List $list
 *   Reference to the FeatureList that this feature belongs to
 */

class Feature
{
  /**
   * Fragment identifiers to be defined for quickly accessing
   * the feature description.
   *
   * @var Array[String]
   */

  protected $anchors = array();
 
  /**
   * Value of the explanatory <code>title</code> attribute for the feature.
   *
   * @var string
   */

  protected $title = '';
 
  /**
   * Name or example code of the feature
   *
   * @var string
   */

  protected $content = '';
   
  /**
   * Description of the feature.  Displayed directly if code is missing,
   * otherwise used as `title' attribute value.
   *
   * @var string
   */

  protected $descr = '';
 
  /**
   * Versions that support this feature
   *
   * @var Array
   */

  protected $versions = array();
   
  /**
   * Reference to the FeatureList that this feature belongs to
   *
   * @var FeatureList
   */

  protected $list = null;
 
  public function setList(&$oList)
  {
    $this->list =& $oList;
  }

  /**
   * Creates a new Feature object, using values from the passed parameters
   * array.
   *
   * @param array|Object $params
   * @return Feature
   */

  public function __construct($params = array())
  {
    $aVars = get_class_vars(__CLASS__);
   
    foreach ($aVars as $key => $value)
    {
      if (isset($params[$key]))
      {
        $this->$key = $params[$key];
      }
    }
  }

  /*
  * Protected properties may be read, but not written
  */

  public function __get($property)
  {
    if (property_exists(get_class($this), $property))
    {
      return $this->$property;
    }
  }
 
  /**
   * Determines whether one version is greater than another.
   *
   * @param string $v1  Version string #1
   * @param string $v2  Version string #2
   * @return bool
   *   <code>true</code> if the version <var>$v1</var> is greater than
   *   the version <var>$v2</var>, <code>false</code> otherwise
   */

  protected static function _versionIsGreater($v1, $v2)
  {
    $v1 = explode('.', $v1);
    $v2 = explode('.', $v2);
   
    foreach ($v1 as $key => $value)
    {
      if ((int)$value <= (int)$v2[$key])
      {
        return false;
      }
    }
   
    return true;
  }
 
  /**
   * Returns <code>' class="safe"'</code> if the feature
   * can be considered safe.  The required information
   * is stored in the <code>safeVersions</code> property
   * of the associated <code>FeatureList</code> object.
   *
   * @return string
   * @see FeatureList::defaultSafeVersions
   */

  protected function getSafeStr()
  {
    if (!is_null($this->list))
    {
      foreach ($this->list->safeVersions as $impl => &$safeVer)
      {
        $thisImplVer =& $this->versions[$impl];
        if (is_array($thisImplVer))
        {
          if (isset($thisImplVer['tested']) && !is_bool($thisImplVer['tested']))
          {
            $thisImplVer =& $thisImplVer['tested'];
          }
          else
        {
            $thisImplVer =& $thisImplVer[0];
          }
        }
       
        /* DEBUG */
        // echo " $impl=$thisImplVer ";
       
        if (preg_match('/^-?$/', $thisImplVer) || self::_versionIsGreater($thisImplVer, $safeVer))
        {
          return '';
        }
      }
     
      return ' class="safe"';
    }
    else
    {
      return '';
    }
  }
 
  protected function getTitleStr()
  {
    if (!empty($this->title))
    {
      return " title=\"{$this->title}\"";
    }
    else
    {
      return '';
    }
  }
 
  protected function getAnchors()
  {
    $result = array();
 
    foreach ($this->anchors as $anchor)
    {
      $result[] = "<a name=\"{$anchor}\"";
     
      if (preg_match('/^[a-z][a-z0-9_:.-]*/i', $anchor))
      {
        $result[] = " id=\"{$anchor}\"";
      }
     
      $result[] = '></a>';
    }
   
    return join('', $result);
  }

  protected function getAssumed($v)
  {
    if (is_array($v) && isset($v['assumed']) && $v['assumed'])
    {
      return ' class="assumed"';
    }
   
    return '';
  }

  protected function getTested($v)
  {
    if (is_array($v) && isset($v['tested']) && $v['tested'])
    {
      return ' class="tested"';
    }
   
    return '';
  }
 
  /**
   * Returns the version of a feature.
   *
   * @param string|VersionInfo $vInfo
   * @return mixed
   */

  protected function getVer($vInfo)
  {
    if (is_array($vInfo))
    {
      /* TODO: Return all versions: documented, assumed, and tested */
      $vNumber = (isset($vInfo['tested'])
                  && gettype($vInfo['tested']) !== 'boolean')
                     ? $vInfo['tested']
                     : $vInfo[0];
      $section = isset($vInfo['section'])
                   ? ' <span class="section" title="Specification section">['
                      . $vInfo['section'] . ']</span>'
                   : '';
     
      if (isset($vInfo['urn']))
      {
        if ($this->list instanceof FeatureList)
        {
          $url = $this->list->resolveURN($vInfo['urn']);
          $vNumber = '<a href="' . $url . '">' . $vNumber
            . ($section ? $section : '') . '</a>';
        }
      }
      else if ($section)
      {
        $vNumber .= $section;
      }

      $vInfo = $vNumber;
    }

    return ($vInfo === '-')
      ? '<span title="Not supported">&#8722;</span>'
      : $vInfo;
  }
 
  /**
   * Returns a syntax-highlighted version of a string
   *
   * @param string $s
   * @return string
   */

  protected static function shl($s)
  {
    /* stub */
    return $s;
  }
 
  public function printMe()
  {
    ?>
<tr<?php echo $this->getSafeStr(); ?>>
          <th<?php echo $this->getTitleStr(); ?>><?php
            echo $this->getAnchors();
            echo /*preg_replace_callback(
              '#(<code>)(.+?)(</code>)#',
              array('self', 'shl'),*/

              preg_replace('/&hellip;/', '&#8230;', $this->content)/*)*/;
            ?></th>
<?php
    $versions = $this->versions;
    $testcase = false;
    if (!is_null($this->list))
    {
      $versions =& $this->list->versions;
      $testcase = $this->list->testcase;
    }

    static $row = 0;
    $row++;
   
    $column = 0;
    $thisVersions =& $this->versions;
   
    foreach ($versions as $key => $value)
    {
      $column++;
      $id = "td$row-$column";
      $ver = isset($thisVersions[$key]) ? $thisVersions[$key] : '';
      if ($key || $testcase)
      {
?>
          <td<?php
            if (!$key)
            {
              echo " id='$id'";
            }
           
            echo $this->getAssumed($ver) . $this->getTested($ver);
           
            if (!$key)
            {
              if (!empty($ver))
              {
                echo ' title="Test code: '
                  . htmlspecialchars(
                      preg_replace('/\\\(["\'])/', '\1',
                        reduceWhitespace($ver)
                      ),
                      ENT_COMPAT,
                      FEATURES_ENCODING
                    )
                  . '"';
              }
              else
            {
                echo ' title="Not applicable: No automated test case'
                  . ' is available for this feature.  If possible, please'
                  . ' click the feature code in the first column to run'
                  . ' a manual test."';
              }
            }
            else
          {
              echo ' title="'
                . htmlspecialchars(
                    preg_replace('/<.*?>/', '', $value),
                    ENT_COMPAT, FEATURES_ENCODING)
                . '"';
            }
            ?>><?php
            if ($key)
            {
              echo $this->getVer($ver);
             
              /* General footnotes support: include footnotes.class.php to enable */
              if (is_array($ver) && isset($ver['footnote']) && $ver['footnote'])
              {
                echo $ver['footnote'];
              }
            }
            else
          {
              if (!empty($ver) && $testcase)
              {
                ?><script type="text/javascript">
  // <![CDATA[
  var s = test(<?php echo $ver; ?>, '<span title="Supported">+<\/span>',
    '<span title="Not supported">&#8722;<\/span>');
  jsx.tryThis("document.write(s);",
          "document.getElementById('<?php echo $id; ?>').appendChild("
          + "document.createTextNode(s));");
  // ]]>
</script><?php
              }
              else
            {
                echo '<abbr>N/A</abbr>';
              }
            }
            ?></td>
<?php
      }
    }
?>
        </tr>
<?php
  }
}
 
?>