<?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">−</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('/…/', '…', $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; ?>,
'<input title="Supported" name="
<?php echo htmlspecialchars($this->title, ENT_COMPAT, FEATURES_ENCODING
); ?>" value="+" readonly>',
'<input title="Not supported" name="
<?php echo htmlspecialchars($this->title, ENT_COMPAT, FEATURES_ENCODING
); ?>" value="−" readonly>');
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
}
}
?>