1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the league/commonmark package. 7 * 8 * (c) Colin O'Dell <colinodell@gmail.com> 9 * 10 * For the full copyright and license information, please view the LICENSE 11 * file that was distributed with this source code. 12 */ 13 14namespace League\CommonMark\Input; 15 16use League\CommonMark\Exception\UnexpectedEncodingException; 17 18class MarkdownInput implements MarkdownInputInterface 19{ 20 /** 21 * @var array<int, string>|null 22 * 23 * @psalm-readonly-allow-private-mutation 24 */ 25 private ?array $lines = null; 26 27 /** @psalm-readonly-allow-private-mutation */ 28 private string $content; 29 30 /** @psalm-readonly-allow-private-mutation */ 31 private ?int $lineCount = null; 32 33 /** @psalm-readonly */ 34 private int $lineOffset; 35 36 public function __construct(string $content, int $lineOffset = 0) 37 { 38 if (! \mb_check_encoding($content, 'UTF-8')) { 39 throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected'); 40 } 41 42 // Strip any leading UTF-8 BOM 43 if (\substr($content, 0, 3) === "\xEF\xBB\xBF") { 44 $content = \substr($content, 3); 45 } 46 47 $this->content = $content; 48 $this->lineOffset = $lineOffset; 49 } 50 51 public function getContent(): string 52 { 53 return $this->content; 54 } 55 56 /** 57 * {@inheritDoc} 58 */ 59 public function getLines(): iterable 60 { 61 $this->splitLinesIfNeeded(); 62 63 \assert($this->lines !== null); 64 65 /** @psalm-suppress PossiblyNullIterator */ 66 foreach ($this->lines as $i => $line) { 67 yield $this->lineOffset + $i + 1 => $line; 68 } 69 } 70 71 public function getLineCount(): int 72 { 73 $this->splitLinesIfNeeded(); 74 75 \assert($this->lineCount !== null); 76 77 return $this->lineCount; 78 } 79 80 private function splitLinesIfNeeded(): void 81 { 82 if ($this->lines !== null) { 83 return; 84 } 85 86 $lines = \preg_split('/\r\n|\n|\r/', $this->content); 87 if ($lines === false) { 88 throw new UnexpectedEncodingException('Failed to split Markdown content by line'); 89 } 90 91 $this->lines = $lines; 92 93 // Remove any newline which appears at the very end of the string. 94 // We've already split the document by newlines, so we can simply drop 95 // any empty element which appears on the end. 96 if (\end($this->lines) === '') { 97 \array_pop($this->lines); 98 } 99 100 $this->lineCount = \count($this->lines); 101 } 102} 103