1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/dolibarr_ynh.git synced 2024-09-03 18:35:53 +02:00
dolibarr_ynh/sources/dolibarr/htdocs/includes/restler/CommentParser.php

467 lines
14 KiB
PHP
Raw Normal View History

2015-09-28 22:09:38 +02:00
<?php
namespace Luracast\Restler;
use Exception;
/**
* Parses the PHPDoc comments for metadata. Inspired by `Documentor` code base.
*
* @category Framework
* @package Restler
* @subpackage Helper
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class CommentParser
{
/**
* name for the embedded data
*
* @var string
*/
public static $embeddedDataName = 'properties';
/**
* Regular Expression pattern for finding the embedded data and extract
* the inner information. It is used with preg_match.
*
* @var string
*/
public static $embeddedDataPattern
= '/```(\w*)[\s]*(([^`]*`{0,2}[^`]+)*)```/ms';
/**
* Pattern will have groups for the inner details of embedded data
* this index is used to locate the data portion.
*
* @var int
*/
public static $embeddedDataIndex = 2;
/**
* Delimiter used to split the array data.
*
* When the name portion is of the embedded data is blank auto detection
* will be used and if URLEncodedFormat is detected as the data format
* the character specified will be used as the delimiter to find split
* array data.
*
* @var string
*/
public static $arrayDelimiter = ',';
/**
* character sequence used to escape \@
*/
const escapedAtChar = '\\@';
/**
* character sequence used to escape end of comment
*/
const escapedCommendEnd = '{@*}';
/**
* Instance of Restler class injected at runtime.
*
* @var Restler
*/
public $restler;
/**
* Comment information is parsed and stored in to this array.
*
* @var array
*/
private $_data = array();
/**
* Parse the comment and extract the data.
*
* @static
*
* @param $comment
* @param bool $isPhpDoc
*
* @return array associative array with the extracted values
*/
public static function parse($comment, $isPhpDoc = true)
{
$p = new self();
if (empty($comment)) {
return $p->_data;
}
if ($isPhpDoc) {
$comment = self::removeCommentTags($comment);
}
$p->extractData($comment);
return $p->_data;
}
/**
* Removes the comment tags from each line of the comment.
*
* @static
*
* @param string $comment PhpDoc style comment
*
* @return string comments with out the tags
*/
public static function removeCommentTags($comment)
{
$pattern = '/(^\/\*\*)|(^\s*\**[ \/]?)|\s(?=@)|\s\*\//m';
return preg_replace($pattern, '', $comment);
}
/**
* Extracts description and long description, uses other methods to get
* parameters.
*
* @param $comment
*
* @return array
*/
private function extractData($comment)
{
//to use @ as part of comment we need to
$comment = str_replace(
array(self::escapedCommendEnd, self::escapedAtChar),
array('*/', '@'),
$comment);
$description = array();
$longDescription = array();
$params = array();
$mode = 0; // extract short description;
$comments = preg_split("/(\r?\n)/", $comment);
// remove first blank line;
array_shift($comments);
$addNewline = false;
foreach ($comments as $line) {
$line = trim($line);
$newParam = false;
if (empty ($line)) {
if ($mode == 0) {
$mode++;
} else {
$addNewline = true;
}
continue;
} elseif ($line{0} == '@') {
$mode = 2;
$newParam = true;
}
switch ($mode) {
case 0 :
$description[] = $line;
if (count($description) > 3) {
// if more than 3 lines take only first line
$longDescription = $description;
$description[] = array_shift($longDescription);
$mode = 1;
} elseif (substr($line, -1) == '.') {
$mode = 1;
}
break;
case 1 :
if ($addNewline) {
$line = ' ' . $line;
}
$longDescription[] = $line;
break;
case 2 :
$newParam
? $params[] = $line
: $params[count($params) - 1] .= ' ' . $line;
}
$addNewline = false;
}
$description = implode(' ', $description);
$longDescription = implode(' ', $longDescription);
$description = preg_replace('/\s+/msu', ' ', $description);
$longDescription = preg_replace('/\s+/msu', ' ', $longDescription);
list($description, $d1)
= $this->parseEmbeddedData($description);
list($longDescription, $d2)
= $this->parseEmbeddedData($longDescription);
$this->_data = compact('description', 'longDescription');
$d2 += $d1;
if (!empty($d2)) {
$this->_data[self::$embeddedDataName] = $d2;
}
foreach ($params as $key => $line) {
list(, $param, $value) = preg_split('/\@|\s/', $line, 3)
+ array('', '', '');
list($value, $embedded) = $this->parseEmbeddedData($value);
$value = array_filter(preg_split('/\s+/msu', $value));
$this->parseParam($param, $value, $embedded);
}
return $this->_data;
}
/**
* Parse parameters that begin with (at)
*
* @param $param
* @param array $value
* @param array $embedded
*/
private function parseParam($param, array $value, array $embedded)
{
$data = & $this->_data;
$allowMultiple = false;
switch ($param) {
case 'param' :
$value = $this->formatParam($value);
$allowMultiple = true;
break;
case 'var' :
$value = $this->formatVar($value);
break;
case 'return' :
$value = $this->formatReturn($value);
break;
case 'class' :
$data = & $data[$param];
list ($param, $value) = $this->formatClass($value);
break;
case 'access' :
$value = reset($value);
break;
case 'expires' :
case 'status' :
$value = intval(reset($value));
break;
case 'throws' :
$value = $this->formatThrows($value);
$allowMultiple = true;
break;
case 'author':
$value = $this->formatAuthor($value);
$allowMultiple = true;
break;
case 'header' :
case 'link':
case 'example':
case 'todo':
$allowMultiple = true;
//don't break, continue with code for default:
default :
$value = implode(' ', $value);
}
if (!empty($embedded)) {
if (is_string($value)) {
$value = array('description' => $value);
}
$value[self::$embeddedDataName] = $embedded;
}
if (empty ($data[$param])) {
if ($allowMultiple) {
$data[$param] = array(
$value
);
} else {
$data[$param] = $value;
}
} elseif ($allowMultiple) {
$data[$param][] = $value;
} elseif ($param == 'param') {
$arr = array(
$data[$param],
$value
);
$data[$param] = $arr;
} else {
if (!is_string($value) && isset($value[self::$embeddedDataName])
&& isset($data[$param][self::$embeddedDataName])
) {
$value[self::$embeddedDataName]
+= $data[$param][self::$embeddedDataName];
}
$data[$param] = $value + $data[$param];
}
}
/**
* Parses the inline php doc comments and embedded data.
*
* @param $subject
*
* @return array
* @throws Exception
*/
private function parseEmbeddedData($subject)
{
$data = array();
//parse {@pattern } tags specially
while (preg_match('|(?s-m)({@pattern (/.+/[imsxuADSUXJ]*)})|', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$data['pattern'] = $matches[2];
}
while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
if ($matches[2] == 'true' || $matches[2] == 'false') {
$matches[2] = $matches[2] == 'true';
} elseif ($matches[2] == '') {
$matches[2] = true;
}
if ($matches[1] == 'pattern') {
throw new Exception('Inline pattern tag should follow {@pattern /REGEX_PATTERN_HERE/} format and can optionally include PCRE modifiers following the ending `/`');
} elseif (false !== strpos($matches[2], static::$arrayDelimiter)) {
$matches[2] = explode(static::$arrayDelimiter, $matches[2]);
}
$data[$matches[1]] = $matches[2];
}
while (preg_match(self::$embeddedDataPattern, $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$str = $matches[self::$embeddedDataIndex];
if (isset ($this->restler)
&& self::$embeddedDataIndex > 1
&& !empty ($matches[1])
) {
$extension = $matches[1];
$formatMap = $this->restler->getFormatMap();
if (isset ($formatMap[$extension])) {
/**
* @var \Luracast\Restler\Format\iFormat
*/
$format = $formatMap[$extension];
$format = new $format();
$data = $format->decode($str);
}
} else { // auto detect
if ($str{0} == '{') {
$d = json_decode($str, true);
if (json_last_error() != JSON_ERROR_NONE) {
throw new Exception('Error parsing embedded JSON data'
. " $str");
}
$data = $d + $data;
} else {
parse_str($str, $d);
//clean up
$d = array_filter($d);
foreach ($d as $key => $val) {
$kt = trim($key);
if ($kt != $key) {
unset($d[$key]);
$key = $kt;
$d[$key] = $val;
}
if (is_string($val)) {
if ($val == 'true' || $val == 'false') {
$d[$key] = $val == 'true' ? true : false;
} else {
$val = explode(self::$arrayDelimiter, $val);
if (count($val) > 1) {
$d[$key] = $val;
} else {
$d[$key] =
preg_replace('/\s+/msu', ' ',
$d[$key]);
}
}
}
}
$data = $d + $data;
}
}
}
return array($subject, $data);
}
private function formatThrows(array $value)
{
$r = array();
$r['code'] = count($value) && is_numeric($value[0])
? intval(array_shift($value)) : 500;
$reason = implode(' ', $value);
$r['reason'] = empty($reason) ? '' : $reason;
return $r;
}
private function formatClass(array $value)
{
$param = array_shift($value);
if (empty($param)) {
$param = 'Unknown';
}
$value = implode(' ', $value);
return array(
ltrim($param, '\\'),
array('description' => $value)
);
}
private function formatAuthor(array $value)
{
$r = array();
$email = end($value);
if ($email{0} == '<') {
$email = substr($email, 1, -1);
array_pop($value);
$r['email'] = $email;
}
$r['name'] = implode(' ', $value);
return $r;
}
private function formatReturn(array $value)
{
$data = explode('|', array_shift($value));
$r = array(
'type' => count($data) == 1 ? $data[0] : $data
);
$r['description'] = implode(' ', $value);
return $r;
}
private function formatParam(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
$data = array_shift($value);
if (!empty($data) && $data{0} == '$') {
$r['name'] = substr($data, 1);
}
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
private function formatVar(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
}