annotate vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 7a779792577d
children
rev   line source
Chris@12 1 <?php
Chris@12 2 /**
Chris@12 3 * This file is part of phpDocumentor.
Chris@12 4 *
Chris@12 5 * For the full copyright and license information, please view the LICENSE
Chris@12 6 * file that was distributed with this source code.
Chris@12 7 *
Chris@12 8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
Chris@12 9 * @license http://www.opensource.org/licenses/mit-license.php MIT
Chris@12 10 * @link http://phpdoc.org
Chris@12 11 */
Chris@12 12
Chris@12 13 namespace phpDocumentor\Reflection\DocBlock;
Chris@12 14
Chris@12 15 use phpDocumentor\Reflection\Types\Context as TypeContext;
Chris@12 16
Chris@12 17 /**
Chris@12 18 * Creates a new Description object given a body of text.
Chris@12 19 *
Chris@12 20 * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
Chris@12 21 * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
Chris@12 22 * Description object's `render` method.
Chris@12 23 *
Chris@12 24 * In addition to the above does a Description support two types of escape sequences:
Chris@12 25 *
Chris@12 26 * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
Chris@12 27 * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
Chris@12 28 * of an inline tag.
Chris@12 29 *
Chris@12 30 * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
Chris@12 31 * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
Chris@12 32 * over unexpected spaces as can be observed with tag descriptions.
Chris@12 33 */
Chris@12 34 class DescriptionFactory
Chris@12 35 {
Chris@12 36 /** @var TagFactory */
Chris@12 37 private $tagFactory;
Chris@12 38
Chris@12 39 /**
Chris@12 40 * Initializes this factory with the means to construct (inline) tags.
Chris@12 41 *
Chris@12 42 * @param TagFactory $tagFactory
Chris@12 43 */
Chris@12 44 public function __construct(TagFactory $tagFactory)
Chris@12 45 {
Chris@12 46 $this->tagFactory = $tagFactory;
Chris@12 47 }
Chris@12 48
Chris@12 49 /**
Chris@12 50 * Returns the parsed text of this description.
Chris@12 51 *
Chris@12 52 * @param string $contents
Chris@12 53 * @param TypeContext $context
Chris@12 54 *
Chris@12 55 * @return Description
Chris@12 56 */
Chris@12 57 public function create($contents, TypeContext $context = null)
Chris@12 58 {
Chris@12 59 list($text, $tags) = $this->parse($this->lex($contents), $context);
Chris@12 60
Chris@12 61 return new Description($text, $tags);
Chris@12 62 }
Chris@12 63
Chris@12 64 /**
Chris@12 65 * Strips the contents from superfluous whitespace and splits the description into a series of tokens.
Chris@12 66 *
Chris@12 67 * @param string $contents
Chris@12 68 *
Chris@12 69 * @return string[] A series of tokens of which the description text is composed.
Chris@12 70 */
Chris@12 71 private function lex($contents)
Chris@12 72 {
Chris@12 73 $contents = $this->removeSuperfluousStartingWhitespace($contents);
Chris@12 74
Chris@12 75 // performance optimalization; if there is no inline tag, don't bother splitting it up.
Chris@12 76 if (strpos($contents, '{@') === false) {
Chris@12 77 return [$contents];
Chris@12 78 }
Chris@12 79
Chris@12 80 return preg_split(
Chris@12 81 '/\{
Chris@12 82 # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
Chris@12 83 (?!@\})
Chris@12 84 # We want to capture the whole tag line, but without the inline tag delimiters.
Chris@12 85 (\@
Chris@12 86 # Match everything up to the next delimiter.
Chris@12 87 [^{}]*
Chris@12 88 # Nested inline tag content should not be captured, or it will appear in the result separately.
Chris@12 89 (?:
Chris@12 90 # Match nested inline tags.
Chris@12 91 (?:
Chris@12 92 # Because we did not catch the tag delimiters earlier, we must be explicit with them here.
Chris@12 93 # Notice that this also matches "{}", as a way to later introduce it as an escape sequence.
Chris@12 94 \{(?1)?\}
Chris@12 95 |
Chris@12 96 # Make sure we match hanging "{".
Chris@12 97 \{
Chris@12 98 )
Chris@12 99 # Match content after the nested inline tag.
Chris@12 100 [^{}]*
Chris@12 101 )* # If there are more inline tags, match them as well. We use "*" since there may not be any
Chris@12 102 # nested inline tags.
Chris@12 103 )
Chris@12 104 \}/Sux',
Chris@12 105 $contents,
Chris@12 106 null,
Chris@12 107 PREG_SPLIT_DELIM_CAPTURE
Chris@12 108 );
Chris@12 109 }
Chris@12 110
Chris@12 111 /**
Chris@12 112 * Parses the stream of tokens in to a new set of tokens containing Tags.
Chris@12 113 *
Chris@12 114 * @param string[] $tokens
Chris@12 115 * @param TypeContext $context
Chris@12 116 *
Chris@12 117 * @return string[]|Tag[]
Chris@12 118 */
Chris@12 119 private function parse($tokens, TypeContext $context)
Chris@12 120 {
Chris@12 121 $count = count($tokens);
Chris@12 122 $tagCount = 0;
Chris@12 123 $tags = [];
Chris@12 124
Chris@12 125 for ($i = 1; $i < $count; $i += 2) {
Chris@12 126 $tags[] = $this->tagFactory->create($tokens[$i], $context);
Chris@12 127 $tokens[$i] = '%' . ++$tagCount . '$s';
Chris@12 128 }
Chris@12 129
Chris@12 130 //In order to allow "literal" inline tags, the otherwise invalid
Chris@12 131 //sequence "{@}" is changed to "@", and "{}" is changed to "}".
Chris@12 132 //"%" is escaped to "%%" because of vsprintf.
Chris@12 133 //See unit tests for examples.
Chris@12 134 for ($i = 0; $i < $count; $i += 2) {
Chris@12 135 $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]);
Chris@12 136 }
Chris@12 137
Chris@12 138 return [implode('', $tokens), $tags];
Chris@12 139 }
Chris@12 140
Chris@12 141 /**
Chris@12 142 * Removes the superfluous from a multi-line description.
Chris@12 143 *
Chris@12 144 * When a description has more than one line then it can happen that the second and subsequent lines have an
Chris@12 145 * additional indentation. This is commonly in use with tags like this:
Chris@12 146 *
Chris@12 147 * {@}since 1.1.0 This is an example
Chris@12 148 * description where we have an
Chris@12 149 * indentation in the second and
Chris@12 150 * subsequent lines.
Chris@12 151 *
Chris@12 152 * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
Chris@12 153 * lines and this may cause rendering issues when, for example, using a Markdown converter.
Chris@12 154 *
Chris@12 155 * @param string $contents
Chris@12 156 *
Chris@12 157 * @return string
Chris@12 158 */
Chris@12 159 private function removeSuperfluousStartingWhitespace($contents)
Chris@12 160 {
Chris@12 161 $lines = explode("\n", $contents);
Chris@12 162
Chris@12 163 // if there is only one line then we don't have lines with superfluous whitespace and
Chris@12 164 // can use the contents as-is
Chris@12 165 if (count($lines) <= 1) {
Chris@12 166 return $contents;
Chris@12 167 }
Chris@12 168
Chris@12 169 // determine how many whitespace characters need to be stripped
Chris@12 170 $startingSpaceCount = 9999999;
Chris@12 171 for ($i = 1; $i < count($lines); $i++) {
Chris@12 172 // lines with a no length do not count as they are not indented at all
Chris@12 173 if (strlen(trim($lines[$i])) === 0) {
Chris@12 174 continue;
Chris@12 175 }
Chris@12 176
Chris@12 177 // determine the number of prefixing spaces by checking the difference in line length before and after
Chris@12 178 // an ltrim
Chris@12 179 $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i])));
Chris@12 180 }
Chris@12 181
Chris@12 182 // strip the number of spaces from each line
Chris@12 183 if ($startingSpaceCount > 0) {
Chris@12 184 for ($i = 1; $i < count($lines); $i++) {
Chris@12 185 $lines[$i] = substr($lines[$i], $startingSpaceCount);
Chris@12 186 }
Chris@12 187 }
Chris@12 188
Chris@12 189 return implode("\n", $lines);
Chris@12 190 }
Chris@12 191 }