242 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| declare(strict_types=1);
 | |
| 
 | |
| namespace Jfcherng\Diff\Renderer;
 | |
| 
 | |
| use Jfcherng\Diff\Differ;
 | |
| use Jfcherng\Diff\SequenceMatcher;
 | |
| use Jfcherng\Diff\Utility\Language;
 | |
| 
 | |
| /**
 | |
|  * Base class for diff renderers.
 | |
|  *
 | |
|  * @todo use typed properties (BC breaking for public interface) in v7
 | |
|  */
 | |
| abstract class AbstractRenderer implements RendererInterface
 | |
| {
 | |
|     /**
 | |
|      * @var array information about this renderer
 | |
|      */
 | |
|     public const INFO = [
 | |
|         'desc' => 'default_desc',
 | |
|         'type' => 'default_type',
 | |
|     ];
 | |
| 
 | |
|     /**
 | |
|      * @var bool Is this renderer pure text?
 | |
|      */
 | |
|     public const IS_TEXT_RENDERER = true;
 | |
| 
 | |
|     /**
 | |
|      * @var string[] array of the opcodes and their corresponding symbols
 | |
|      */
 | |
|     public const SYMBOL_MAP = [
 | |
|         SequenceMatcher::OP_DEL => '-',
 | |
|         SequenceMatcher::OP_EQ => ' ',
 | |
|         SequenceMatcher::OP_INS => '+',
 | |
|         SequenceMatcher::OP_REP => '!',
 | |
|     ];
 | |
| 
 | |
|     /**
 | |
|      * @var Language the language translation object
 | |
|      */
 | |
|     protected $t;
 | |
| 
 | |
|     /**
 | |
|      * If the input "changes" have `<ins>...</ins>` or `<del>...</del>`,
 | |
|      * which means they have been processed, then `false`. Otherwise, `true`.
 | |
|      *
 | |
|      * @var bool
 | |
|      */
 | |
|     protected $changesAreRaw = true;
 | |
| 
 | |
|     /**
 | |
|      * @var array array of the default options that apply to this renderer
 | |
|      */
 | |
|     protected static $defaultOptions = [
 | |
|         // how detailed the rendered HTML in-line diff is? (none, line, word, char)
 | |
|         'detailLevel' => 'line',
 | |
|         // renderer language: eng, cht, chs, jpn, ...
 | |
|         // or an array which has the same keys with a language file
 | |
|         // check the "Custom Language" section in the readme for more advanced usage
 | |
|         'language' => 'eng',
 | |
|         // show line numbers in HTML renderers
 | |
|         'lineNumbers' => true,
 | |
|         // show a separator between different diff hunks in HTML renderers
 | |
|         'separateBlock' => true,
 | |
|         // show the (table) header
 | |
|         'showHeader' => true,
 | |
|         // convert spaces/tabs into HTML codes like `<span class="ch sp"> </span>`
 | |
|         // and the frontend is responsible for rendering them with CSS.
 | |
|         // when using this, "spacesToNbsp" should be false and "tabSize" is not respected.
 | |
|         'spaceToHtmlTag' => false,
 | |
|         // the frontend HTML could use CSS "white-space: pre;" to visualize consecutive whitespaces
 | |
|         // but if you want to visualize them in the backend with " ", you can set this to true
 | |
|         'spacesToNbsp' => false,
 | |
|         // HTML renderer tab width (negative = do not convert into spaces)
 | |
|         'tabSize' => 4,
 | |
|         // this option is currently only for the Combined renderer.
 | |
|         // it determines whether a replace-type block should be merged or not
 | |
|         // depending on the content changed ratio, which values between 0 and 1.
 | |
|         'mergeThreshold' => 0.8,
 | |
|         // this option is currently only for the Unified and the Context renderers.
 | |
|         // RendererConstant::CLI_COLOR_AUTO = colorize the output if possible (default)
 | |
|         // RendererConstant::CLI_COLOR_ENABLE = force to colorize the output
 | |
|         // RendererConstant::CLI_COLOR_DISABLE = force not to colorize the output
 | |
|         'cliColorization' => RendererConstant::CLI_COLOR_AUTO,
 | |
|         // this option is currently only for the Json renderer.
 | |
|         // internally, ops (tags) are all int type but this is not good for human reading.
 | |
|         // set this to "true" to convert them into string form before outputting.
 | |
|         'outputTagAsString' => false,
 | |
|         // this option is currently only for the Json renderer.
 | |
|         // it controls how the output JSON is formatted.
 | |
|         // see available options on https://www.php.net/manual/en/function.json-encode.php
 | |
|         'jsonEncodeFlags' => \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE,
 | |
|         // this option is currently effective when the "detailLevel" is "word"
 | |
|         // characters listed in this array can be used to make diff segments into a whole
 | |
|         // for example, making "<del>good</del>-<del>looking</del>" into "<del>good-looking</del>"
 | |
|         // this should bring better readability but set this to empty array if you do not want it
 | |
|         'wordGlues' => ['-', ' '],
 | |
|         // change this value to a string as the returned diff if the two input strings are identical
 | |
|         'resultForIdenticals' => null,
 | |
|         // extra HTML classes added to the DOM of the diff container
 | |
|         'wrapperClasses' => ['diff-wrapper'],
 | |
|     ];
 | |
| 
 | |
|     /**
 | |
|      * @var array array containing the user applied and merged default options for the renderer
 | |
|      */
 | |
|     protected $options = [];
 | |
| 
 | |
|     /**
 | |
|      * The constructor. Instantiates the rendering engine and if options are passed,
 | |
|      * sets the options for the renderer.
 | |
|      *
 | |
|      * @param array $options optionally, an array of the options for the renderer
 | |
|      */
 | |
|     public function __construct(array $options = [])
 | |
|     {
 | |
|         $this->setOptions($options);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the options of the renderer to those supplied in the passed in array.
 | |
|      * Options are merged with the default to ensure that there aren't any missing
 | |
|      * options.
 | |
|      *
 | |
|      * @param array $options the options
 | |
|      *
 | |
|      * @return static
 | |
|      */
 | |
|     public function setOptions(array $options): self
 | |
|     {
 | |
|         $newOptions = $options + static::$defaultOptions;
 | |
| 
 | |
|         $this->updateLanguage(
 | |
|             $this->options['language'] ?? '',
 | |
|             $newOptions['language'],
 | |
|         );
 | |
| 
 | |
|         $this->options = $newOptions;
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the options.
 | |
|      *
 | |
|      * @return array the options
 | |
|      */
 | |
|     public function getOptions(): array
 | |
|     {
 | |
|         return $this->options;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @final
 | |
|      *
 | |
|      * @todo mark this method with "final" in the next major release
 | |
|      *
 | |
|      * @throws \InvalidArgumentException
 | |
|      */
 | |
|     public function getResultForIdenticals(): string
 | |
|     {
 | |
|         $custom = $this->options['resultForIdenticals'];
 | |
| 
 | |
|         if (isset($custom) && !\is_string($custom)) {
 | |
|             throw new \InvalidArgumentException('renderer option `resultForIdenticals` must be null or string.');
 | |
|         }
 | |
| 
 | |
|         return $custom ?? $this->getResultForIdenticalsDefault();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the renderer default result when the old and the new are the same.
 | |
|      */
 | |
|     abstract public function getResultForIdenticalsDefault(): string;
 | |
| 
 | |
|     final public function render(Differ $differ): string
 | |
|     {
 | |
|         $this->changesAreRaw = true;
 | |
| 
 | |
|         // the "no difference" situation may happen frequently
 | |
|         return $differ->getOldNewComparison() === 0 && !$differ->options['fullContextIfIdentical']
 | |
|             ? $this->getResultForIdenticals()
 | |
|             : $this->renderWorker($differ);
 | |
|     }
 | |
| 
 | |
|     final public function renderArray(array $differArray): string
 | |
|     {
 | |
|         $this->changesAreRaw = false;
 | |
| 
 | |
|         return $this->renderArrayWorker($differArray);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * The real worker for self::render().
 | |
|      *
 | |
|      * @param Differ $differ the differ object
 | |
|      */
 | |
|     abstract protected function renderWorker(Differ $differ): string;
 | |
| 
 | |
|     /**
 | |
|      * The real worker for self::renderArray().
 | |
|      *
 | |
|      * @param array[][] $differArray the differ array
 | |
|      */
 | |
|     abstract protected function renderArrayWorker(array $differArray): string;
 | |
| 
 | |
|     /**
 | |
|      * Update the Language object.
 | |
|      *
 | |
|      * @param string|string[] $old the old language
 | |
|      * @param string|string[] $new the new language
 | |
|      *
 | |
|      * @return static
 | |
|      */
 | |
|     protected function updateLanguage($old, $new): self
 | |
|     {
 | |
|         if (!isset($this->t) || $old !== $new) {
 | |
|             $this->t = new Language($new);
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * A shorthand to do translation.
 | |
|      *
 | |
|      * @param string $text       The text
 | |
|      * @param bool   $escapeHtml Escape the translated text for HTML?
 | |
|      *
 | |
|      * @return string the translated text
 | |
|      */
 | |
|     protected function _(string $text, bool $escapeHtml = true): string
 | |
|     {
 | |
|         $text = $this->t->translate($text);
 | |
| 
 | |
|         return $escapeHtml ? htmlspecialchars($text) : $text;
 | |
|     }
 | |
| }
 |