1<?php 2 3/* 4 * This file is part of the league/commonmark package. 5 * 6 * (c) Colin O'Dell <colinodell@gmail.com> 7 * 8 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) 9 * - (c) John MacFarlane 10 * 11 * For the full copyright and license information, please view the LICENSE 12 * file that was distributed with this source code. 13 */ 14 15namespace League\CommonMark\Block\Element; 16 17use League\CommonMark\ContextInterface; 18use League\CommonMark\Cursor; 19use League\CommonMark\Util\RegexHelper; 20 21class FencedCode extends AbstractStringContainerBlock 22{ 23 /** 24 * @var string 25 */ 26 protected $info; 27 28 /** 29 * @var int 30 */ 31 protected $length; 32 33 /** 34 * @var string 35 */ 36 protected $char; 37 38 /** 39 * @var int 40 */ 41 protected $offset; 42 43 /** 44 * @param int $length 45 * @param string $char 46 * @param int $offset 47 */ 48 public function __construct(int $length, string $char, int $offset) 49 { 50 parent::__construct(); 51 52 $this->length = $length; 53 $this->char = $char; 54 $this->offset = $offset; 55 } 56 57 /** 58 * @return string 59 */ 60 public function getInfo(): string 61 { 62 return $this->info; 63 } 64 65 /** 66 * @return string[] 67 */ 68 public function getInfoWords(): array 69 { 70 return \preg_split('/\s+/', $this->info) ?: []; 71 } 72 73 /** 74 * @return string 75 */ 76 public function getChar(): string 77 { 78 return $this->char; 79 } 80 81 /** 82 * @param string $char 83 * 84 * @return $this 85 */ 86 public function setChar(string $char): self 87 { 88 $this->char = $char; 89 90 return $this; 91 } 92 93 /** 94 * @return int 95 */ 96 public function getLength(): int 97 { 98 return $this->length; 99 } 100 101 /** 102 * @param int $length 103 * 104 * @return $this 105 */ 106 public function setLength(int $length): self 107 { 108 $this->length = $length; 109 110 return $this; 111 } 112 113 /** 114 * @return int 115 */ 116 public function getOffset(): int 117 { 118 return $this->offset; 119 } 120 121 /** 122 * @param int $offset 123 * 124 * @return $this 125 */ 126 public function setOffset(int $offset): self 127 { 128 $this->offset = $offset; 129 130 return $this; 131 } 132 133 public function canContain(AbstractBlock $block): bool 134 { 135 return false; 136 } 137 138 public function isCode(): bool 139 { 140 return true; 141 } 142 143 public function matchesNextLine(Cursor $cursor): bool 144 { 145 if ($this->length === -1) { 146 if ($cursor->isBlank()) { 147 $this->lastLineBlank = true; 148 } 149 150 return false; 151 } 152 153 // Skip optional spaces of fence offset 154 $cursor->match('/^ {0,' . $this->offset . '}/'); 155 156 return true; 157 } 158 159 public function finalize(ContextInterface $context, int $endLineNumber) 160 { 161 parent::finalize($context, $endLineNumber); 162 163 // first line becomes info string 164 $firstLine = $this->strings->first(); 165 if ($firstLine === false) { 166 $firstLine = ''; 167 } 168 169 $this->info = RegexHelper::unescape(\trim($firstLine)); 170 171 if ($this->strings->count() === 1) { 172 $this->finalStringContents = ''; 173 } else { 174 $this->finalStringContents = \implode("\n", $this->strings->slice(1)) . "\n"; 175 } 176 } 177 178 public function handleRemainingContents(ContextInterface $context, Cursor $cursor) 179 { 180 /** @var self $container */ 181 $container = $context->getContainer(); 182 183 // check for closing code fence 184 if ($cursor->getIndent() <= 3 && $cursor->getNextNonSpaceCharacter() === $container->getChar()) { 185 $match = RegexHelper::matchFirst('/^(?:`{3,}|~{3,})(?= *$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition()); 186 if ($match !== null && \strlen($match[0]) >= $container->getLength()) { 187 // don't add closing fence to container; instead, close it: 188 $this->setLength(-1); // -1 means we've passed closer 189 190 return; 191 } 192 } 193 194 $container->addLine($cursor->getRemainder()); 195 } 196 197 public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool 198 { 199 return false; 200 } 201} 202