"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/symfony/var-dumper/Dumper/HtmlDumper.php" (1 Sep 2020, 32385 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "HtmlDumper.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 /*
    4  * This file is part of the Symfony package.
    5  *
    6  * (c) Fabien Potencier <fabien@symfony.com>
    7  *
    8  * For the full copyright and license information, please view the LICENSE
    9  * file that was distributed with this source code.
   10  */
   11 
   12 namespace Symfony\Component\VarDumper\Dumper;
   13 
   14 use Symfony\Component\VarDumper\Cloner\Cursor;
   15 use Symfony\Component\VarDumper\Cloner\Data;
   16 
   17 /**
   18  * HtmlDumper dumps variables as HTML.
   19  *
   20  * @author Nicolas Grekas <p@tchwork.com>
   21  */
   22 class HtmlDumper extends CliDumper
   23 {
   24     public static $defaultOutput = 'php://output';
   25 
   26     protected static $themes = [
   27         'dark' => [
   28             'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
   29             'num' => 'font-weight:bold; color:#1299DA',
   30             'const' => 'font-weight:bold',
   31             'str' => 'font-weight:bold; color:#56DB3A',
   32             'note' => 'color:#1299DA',
   33             'ref' => 'color:#A0A0A0',
   34             'public' => 'color:#FFFFFF',
   35             'protected' => 'color:#FFFFFF',
   36             'private' => 'color:#FFFFFF',
   37             'meta' => 'color:#B729D9',
   38             'key' => 'color:#56DB3A',
   39             'index' => 'color:#1299DA',
   40             'ellipsis' => 'color:#FF8400',
   41             'ns' => 'user-select:none;',
   42         ],
   43         'light' => [
   44             'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
   45             'num' => 'font-weight:bold; color:#1299DA',
   46             'const' => 'font-weight:bold',
   47             'str' => 'font-weight:bold; color:#629755;',
   48             'note' => 'color:#6897BB',
   49             'ref' => 'color:#6E6E6E',
   50             'public' => 'color:#262626',
   51             'protected' => 'color:#262626',
   52             'private' => 'color:#262626',
   53             'meta' => 'color:#B729D9',
   54             'key' => 'color:#789339',
   55             'index' => 'color:#1299DA',
   56             'ellipsis' => 'color:#CC7832',
   57             'ns' => 'user-select:none;',
   58         ],
   59     ];
   60 
   61     protected $dumpHeader;
   62     protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">';
   63     protected $dumpSuffix = '</pre><script>Sfdump(%s)</script>';
   64     protected $dumpId = 'sf-dump';
   65     protected $colors = true;
   66     protected $headerIsDumped = false;
   67     protected $lastDepth = -1;
   68     protected $styles;
   69 
   70     private $displayOptions = [
   71         'maxDepth' => 1,
   72         'maxStringLength' => 160,
   73         'fileLinkFormat' => null,
   74     ];
   75     private $extraDisplayOptions = [];
   76 
   77     /**
   78      * {@inheritdoc}
   79      */
   80     public function __construct($output = null, string $charset = null, int $flags = 0)
   81     {
   82         AbstractDumper::__construct($output, $charset, $flags);
   83         $this->dumpId = 'sf-dump-'.mt_rand();
   84         $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
   85         $this->styles = static::$themes['dark'] ?? self::$themes['dark'];
   86     }
   87 
   88     /**
   89      * {@inheritdoc}
   90      */
   91     public function setStyles(array $styles)
   92     {
   93         $this->headerIsDumped = false;
   94         $this->styles = $styles + $this->styles;
   95     }
   96 
   97     public function setTheme(string $themeName)
   98     {
   99         if (!isset(static::$themes[$themeName])) {
  100             throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class));
  101         }
  102 
  103         $this->setStyles(static::$themes[$themeName]);
  104     }
  105 
  106     /**
  107      * Configures display options.
  108      *
  109      * @param array $displayOptions A map of display options to customize the behavior
  110      */
  111     public function setDisplayOptions(array $displayOptions)
  112     {
  113         $this->headerIsDumped = false;
  114         $this->displayOptions = $displayOptions + $this->displayOptions;
  115     }
  116 
  117     /**
  118      * Sets an HTML header that will be dumped once in the output stream.
  119      *
  120      * @param string $header An HTML string
  121      */
  122     public function setDumpHeader($header)
  123     {
  124         $this->dumpHeader = $header;
  125     }
  126 
  127     /**
  128      * Sets an HTML prefix and suffix that will encapse every single dump.
  129      *
  130      * @param string $prefix The prepended HTML string
  131      * @param string $suffix The appended HTML string
  132      */
  133     public function setDumpBoundaries($prefix, $suffix)
  134     {
  135         $this->dumpPrefix = $prefix;
  136         $this->dumpSuffix = $suffix;
  137     }
  138 
  139     /**
  140      * {@inheritdoc}
  141      */
  142     public function dump(Data $data, $output = null, array $extraDisplayOptions = [])
  143     {
  144         $this->extraDisplayOptions = $extraDisplayOptions;
  145         $result = parent::dump($data, $output);
  146         $this->dumpId = 'sf-dump-'.mt_rand();
  147 
  148         return $result;
  149     }
  150 
  151     /**
  152      * Dumps the HTML header.
  153      */
  154     protected function getDumpHeader()
  155     {
  156         $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
  157 
  158         if (null !== $this->dumpHeader) {
  159             return $this->dumpHeader;
  160         }
  161 
  162         $line = str_replace('{$options}', json_encode($this->displayOptions, JSON_FORCE_OBJECT), <<<'EOHTML'
  163 <script>
  164 Sfdump = window.Sfdump || (function (doc) {
  165 
  166 var refStyle = doc.createElement('style'),
  167     rxEsc = /([.*+?^${}()|\[\]\/\\])/g,
  168     idRx = /\bsf-dump-\d+-ref[012]\w+\b/,
  169     keyHint = 0 <= navigator.platform.toUpperCase().indexOf('MAC') ? 'Cmd' : 'Ctrl',
  170     addEventListener = function (e, n, cb) {
  171         e.addEventListener(n, cb, false);
  172     };
  173 
  174 (doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle);
  175 
  176 if (!doc.addEventListener) {
  177     addEventListener = function (element, eventName, callback) {
  178         element.attachEvent('on' + eventName, function (e) {
  179             e.preventDefault = function () {e.returnValue = false;};
  180             e.target = e.srcElement;
  181             callback(e);
  182         });
  183     };
  184 }
  185 
  186 function toggle(a, recursive) {
  187     var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass;
  188 
  189     if (/\bsf-dump-compact\b/.test(oldClass)) {
  190         arrow = '▼';
  191         newClass = 'sf-dump-expanded';
  192     } else if (/\bsf-dump-expanded\b/.test(oldClass)) {
  193         arrow = '▶';
  194         newClass = 'sf-dump-compact';
  195     } else {
  196         return false;
  197     }
  198 
  199     if (doc.createEvent && s.dispatchEvent) {
  200         var event = doc.createEvent('Event');
  201         event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false);
  202 
  203         s.dispatchEvent(event);
  204     }
  205 
  206     a.lastChild.innerHTML = arrow;
  207     s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass);
  208 
  209     if (recursive) {
  210         try {
  211             a = s.querySelectorAll('.'+oldClass);
  212             for (s = 0; s < a.length; ++s) {
  213                 if (-1 == a[s].className.indexOf(newClass)) {
  214                     a[s].className = newClass;
  215                     a[s].previousSibling.lastChild.innerHTML = arrow;
  216                 }
  217             }
  218         } catch (e) {
  219         }
  220     }
  221 
  222     return true;
  223 };
  224 
  225 function collapse(a, recursive) {
  226     var s = a.nextSibling || {}, oldClass = s.className;
  227 
  228     if (/\bsf-dump-expanded\b/.test(oldClass)) {
  229         toggle(a, recursive);
  230 
  231         return true;
  232     }
  233 
  234     return false;
  235 };
  236 
  237 function expand(a, recursive) {
  238     var s = a.nextSibling || {}, oldClass = s.className;
  239 
  240     if (/\bsf-dump-compact\b/.test(oldClass)) {
  241         toggle(a, recursive);
  242 
  243         return true;
  244     }
  245 
  246     return false;
  247 };
  248 
  249 function collapseAll(root) {
  250     var a = root.querySelector('a.sf-dump-toggle');
  251     if (a) {
  252         collapse(a, true);
  253         expand(a);
  254 
  255         return true;
  256     }
  257 
  258     return false;
  259 }
  260 
  261 function reveal(node) {
  262     var previous, parents = [];
  263 
  264     while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) {
  265         parents.push(previous);
  266     }
  267 
  268     if (0 !== parents.length) {
  269         parents.forEach(function (parent) {
  270             expand(parent);
  271         });
  272 
  273         return true;
  274     }
  275 
  276     return false;
  277 }
  278 
  279 function highlight(root, activeNode, nodes) {
  280     resetHighlightedNodes(root);
  281 
  282     Array.from(nodes||[]).forEach(function (node) {
  283         if (!/\bsf-dump-highlight\b/.test(node.className)) {
  284             node.className = node.className + ' sf-dump-highlight';
  285         }
  286     });
  287 
  288     if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) {
  289         activeNode.className = activeNode.className + ' sf-dump-highlight-active';
  290     }
  291 }
  292 
  293 function resetHighlightedNodes(root) {
  294     Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) {
  295         strNode.className = strNode.className.replace(/\bsf-dump-highlight\b/, '');
  296         strNode.className = strNode.className.replace(/\bsf-dump-highlight-active\b/, '');
  297     });
  298 }
  299 
  300 return function (root, x) {
  301     root = doc.getElementById(root);
  302 
  303     var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || '  ').replace(rxEsc, '\\$1')+')+', 'm'),
  304         options = {$options},
  305         elt = root.getElementsByTagName('A'),
  306         len = elt.length,
  307         i = 0, s, h,
  308         t = [];
  309 
  310     while (i < len) t.push(elt[i++]);
  311 
  312     for (i in x) {
  313         options[i] = x[i];
  314     }
  315 
  316     function a(e, f) {
  317         addEventListener(root, e, function (e) {
  318             if ('A' == e.target.tagName) {
  319                 f(e.target, e);
  320             } else if ('A' == e.target.parentNode.tagName) {
  321                 f(e.target.parentNode, e);
  322             } else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) {
  323                 f(e.target.nextElementSibling, e, true);
  324             }
  325         });
  326     };
  327     function isCtrlKey(e) {
  328         return e.ctrlKey || e.metaKey;
  329     }
  330     function xpathString(str) {
  331         var parts = str.match(/[^'"]+|['"]/g).map(function (part) {
  332             if ("'" == part)  {
  333                 return '"\'"';
  334             }
  335             if ('"' == part) {
  336                 return "'\"'";
  337             }
  338 
  339             return "'" + part + "'";
  340         });
  341 
  342         return "concat(" + parts.join(",") + ", '')";
  343     }
  344     function xpathHasClass(className) {
  345         return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')";
  346     }
  347     addEventListener(root, 'mouseover', function (e) {
  348         if ('' != refStyle.innerHTML) {
  349             refStyle.innerHTML = '';
  350         }
  351     });
  352     a('mouseover', function (a, e, c) {
  353         if (c) {
  354             e.target.style.cursor = "pointer";
  355         } else if (a = idRx.exec(a.className)) {
  356             try {
  357                 refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}';
  358             } catch (e) {
  359             }
  360         }
  361     });
  362     a('click', function (a, e, c) {
  363         if (/\bsf-dump-toggle\b/.test(a.className)) {
  364             e.preventDefault();
  365             if (!toggle(a, isCtrlKey(e))) {
  366                 var r = doc.getElementById(a.getAttribute('href').substr(1)),
  367                     s = r.previousSibling,
  368                     f = r.parentNode,
  369                     t = a.parentNode;
  370                 t.replaceChild(r, a);
  371                 f.replaceChild(a, s);
  372                 t.insertBefore(s, r);
  373                 f = f.firstChild.nodeValue.match(indentRx);
  374                 t = t.firstChild.nodeValue.match(indentRx);
  375                 if (f && t && f[0] !== t[0]) {
  376                     r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]);
  377                 }
  378                 if (/\bsf-dump-compact\b/.test(r.className)) {
  379                     toggle(s, isCtrlKey(e));
  380                 }
  381             }
  382 
  383             if (c) {
  384             } else if (doc.getSelection) {
  385                 try {
  386                     doc.getSelection().removeAllRanges();
  387                 } catch (e) {
  388                     doc.getSelection().empty();
  389                 }
  390             } else {
  391                 doc.selection.empty();
  392             }
  393         } else if (/\bsf-dump-str-toggle\b/.test(a.className)) {
  394             e.preventDefault();
  395             e = a.parentNode.parentNode;
  396             e.className = e.className.replace(/\bsf-dump-str-(expand|collapse)\b/, a.parentNode.className);
  397         }
  398     });
  399 
  400     elt = root.getElementsByTagName('SAMP');
  401     len = elt.length;
  402     i = 0;
  403 
  404     while (i < len) t.push(elt[i++]);
  405     len = t.length;
  406 
  407     for (i = 0; i < len; ++i) {
  408         elt = t[i];
  409         if ('SAMP' == elt.tagName) {
  410             a = elt.previousSibling || {};
  411             if ('A' != a.tagName) {
  412                 a = doc.createElement('A');
  413                 a.className = 'sf-dump-ref';
  414                 elt.parentNode.insertBefore(a, elt);
  415             } else {
  416                 a.innerHTML += ' ';
  417             }
  418             a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children';
  419             a.innerHTML += '<span>▼</span>';
  420             a.className += ' sf-dump-toggle';
  421 
  422             x = 1;
  423             if ('sf-dump' != elt.parentNode.className) {
  424                 x += elt.parentNode.getAttribute('data-depth')/1;
  425             }
  426             elt.setAttribute('data-depth', x);
  427             var className = elt.className;
  428             elt.className = 'sf-dump-expanded';
  429             if (className ? 'sf-dump-expanded' !== className : (x > options.maxDepth)) {
  430                 toggle(a);
  431             }
  432         } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) {
  433             a = a.substr(1);
  434             elt.className += ' '+a;
  435 
  436             if (/[\[{]$/.test(elt.previousSibling.nodeValue)) {
  437                 a = a != elt.nextSibling.id && doc.getElementById(a);
  438                 try {
  439                     s = a.nextSibling;
  440                     elt.appendChild(a);
  441                     s.parentNode.insertBefore(a, s);
  442                     if (/^[@#]/.test(elt.innerHTML)) {
  443                         elt.innerHTML += ' <span>▶</span>';
  444                     } else {
  445                         elt.innerHTML = '<span>▶</span>';
  446                         elt.className = 'sf-dump-ref';
  447                     }
  448                     elt.className += ' sf-dump-toggle';
  449                 } catch (e) {
  450                     if ('&' == elt.innerHTML.charAt(0)) {
  451                         elt.innerHTML = '…';
  452                         elt.className = 'sf-dump-ref';
  453                     }
  454                 }
  455             }
  456         }
  457     }
  458 
  459     if (doc.evaluate && Array.from && root.children.length > 1) {
  460         root.setAttribute('tabindex', 0);
  461 
  462         SearchState = function () {
  463             this.nodes = [];
  464             this.idx = 0;
  465         };
  466         SearchState.prototype = {
  467             next: function () {
  468                 if (this.isEmpty()) {
  469                     return this.current();
  470                 }
  471                 this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0;
  472 
  473                 return this.current();
  474             },
  475             previous: function () {
  476                 if (this.isEmpty()) {
  477                     return this.current();
  478                 }
  479                 this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1);
  480 
  481                 return this.current();
  482             },
  483             isEmpty: function () {
  484                 return 0 === this.count();
  485             },
  486             current: function () {
  487                 if (this.isEmpty()) {
  488                     return null;
  489                 }
  490                 return this.nodes[this.idx];
  491             },
  492             reset: function () {
  493                 this.nodes = [];
  494                 this.idx = 0;
  495             },
  496             count: function () {
  497                 return this.nodes.length;
  498             },
  499         };
  500 
  501         function showCurrent(state)
  502         {
  503             var currentNode = state.current(), currentRect, searchRect;
  504             if (currentNode) {
  505                 reveal(currentNode);
  506                 highlight(root, currentNode, state.nodes);
  507                 if ('scrollIntoView' in currentNode) {
  508                     currentNode.scrollIntoView(true);
  509                     currentRect = currentNode.getBoundingClientRect();
  510                     searchRect = search.getBoundingClientRect();
  511                     if (currentRect.top < (searchRect.top + searchRect.height)) {
  512                         window.scrollBy(0, -(searchRect.top + searchRect.height + 5));
  513                     }
  514                 }
  515             }
  516             counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count();
  517         }
  518 
  519         var search = doc.createElement('div');
  520         search.className = 'sf-dump-search-wrapper sf-dump-search-hidden';
  521         search.innerHTML = '
  522             <input type="text" class="sf-dump-search-input">
  523             <span class="sf-dump-search-count">0 of 0<\/span>
  524             <button type="button" class="sf-dump-search-input-previous" tabindex="-1">
  525                 <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 1331l-166 165q-19 19-45 19t-45-19L896 965l-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/><\/svg>
  526             <\/button>
  527             <button type="button" class="sf-dump-search-input-next" tabindex="-1">
  528                 <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 808l-742 741q-19 19-45 19t-45-19L109 808q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/><\/svg>
  529             <\/button>
  530         ';
  531         root.insertBefore(search, root.firstChild);
  532 
  533         var state = new SearchState();
  534         var searchInput = search.querySelector('.sf-dump-search-input');
  535         var counter = search.querySelector('.sf-dump-search-count');
  536         var searchInputTimer = 0;
  537         var previousSearchQuery = '';
  538 
  539         addEventListener(searchInput, 'keyup', function (e) {
  540             var searchQuery = e.target.value;
  541             /* Don't perform anything if the pressed key didn't change the query */
  542             if (searchQuery === previousSearchQuery) {
  543                 return;
  544             }
  545             previousSearchQuery = searchQuery;
  546             clearTimeout(searchInputTimer);
  547             searchInputTimer = setTimeout(function () {
  548                 state.reset();
  549                 collapseAll(root);
  550                 resetHighlightedNodes(root);
  551                 if ('' === searchQuery) {
  552                     counter.textContent = '0 of 0';
  553 
  554                     return;
  555                 }
  556 
  557                 var classMatches = [
  558                     "sf-dump-str",
  559                     "sf-dump-key",
  560                     "sf-dump-public",
  561                     "sf-dump-protected",
  562                     "sf-dump-private",
  563                 ].map(xpathHasClass).join(' or ');
  564 
  565                 var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
  566 
  567                 while (node = xpathResult.iterateNext()) state.nodes.push(node);
  568 
  569                 showCurrent(state);
  570             }, 400);
  571         });
  572 
  573         Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) {
  574             addEventListener(btn, 'click', function (e) {
  575                 e.preventDefault();
  576                 -1 !== e.target.className.indexOf('next') ? state.next() : state.previous();
  577                 searchInput.focus();
  578                 collapseAll(root);
  579                 showCurrent(state);
  580             })
  581         });
  582 
  583         addEventListener(root, 'keydown', function (e) {
  584             var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className);
  585             if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) {
  586                 /* F3 or CMD/CTRL + F */
  587                 e.preventDefault();
  588                 search.className = search.className.replace(/\bsf-dump-search-hidden\b/, '');
  589                 searchInput.focus();
  590             } else if (isSearchActive) {
  591                 if (27 === e.keyCode) {
  592                     /* ESC key */
  593                     search.className += ' sf-dump-search-hidden';
  594                     e.preventDefault();
  595                     resetHighlightedNodes(root);
  596                     searchInput.value = '';
  597                 } else if (
  598                     (isCtrlKey(e) && 71 === e.keyCode) /* CMD/CTRL + G */
  599                     || 13 === e.keyCode /* Enter */
  600                     || 114 === e.keyCode /* F3 */
  601                 ) {
  602                     e.preventDefault();
  603                     e.shiftKey ? state.previous() : state.next();
  604                     collapseAll(root);
  605                     showCurrent(state);
  606                 }
  607             }
  608         });
  609     }
  610 
  611     if (0 >= options.maxStringLength) {
  612         return;
  613     }
  614     try {
  615         elt = root.querySelectorAll('.sf-dump-str');
  616         len = elt.length;
  617         i = 0;
  618         t = [];
  619 
  620         while (i < len) t.push(elt[i++]);
  621         len = t.length;
  622 
  623         for (i = 0; i < len; ++i) {
  624             elt = t[i];
  625             s = elt.innerText || elt.textContent;
  626             x = s.length - options.maxStringLength;
  627             if (0 < x) {
  628                 h = elt.innerHTML;
  629                 elt[elt.innerText ? 'innerText' : 'textContent'] = s.substring(0, options.maxStringLength);
  630                 elt.className += ' sf-dump-str-collapse';
  631                 elt.innerHTML = '<span class=sf-dump-str-collapse>'+h+'<a class="sf-dump-ref sf-dump-str-toggle" title="Collapse"> ◀</a></span>'+
  632                     '<span class=sf-dump-str-expand>'+elt.innerHTML+'<a class="sf-dump-ref sf-dump-str-toggle" title="'+x+' remaining characters"> ▶</a></span>';
  633             }
  634         }
  635     } catch (e) {
  636     }
  637 };
  638 
  639 })(document);
  640 </script><style>
  641 pre.sf-dump {
  642     display: block;
  643     white-space: pre;
  644     padding: 5px;
  645     overflow: initial !important;
  646 }
  647 pre.sf-dump:after {
  648    content: "";
  649    visibility: hidden;
  650    display: block;
  651    height: 0;
  652    clear: both;
  653 }
  654 pre.sf-dump span {
  655     display: inline;
  656 }
  657 pre.sf-dump .sf-dump-compact {
  658     display: none;
  659 }
  660 pre.sf-dump abbr {
  661     text-decoration: none;
  662     border: none;
  663     cursor: help;
  664 }
  665 pre.sf-dump a {
  666     text-decoration: none;
  667     cursor: pointer;
  668     border: 0;
  669     outline: none;
  670     color: inherit;
  671 }
  672 pre.sf-dump .sf-dump-ellipsis {
  673     display: inline-block;
  674     overflow: visible;
  675     text-overflow: ellipsis;
  676     max-width: 5em;
  677     white-space: nowrap;
  678     overflow: hidden;
  679     vertical-align: top;
  680 }
  681 pre.sf-dump .sf-dump-ellipsis+.sf-dump-ellipsis {
  682     max-width: none;
  683 }
  684 pre.sf-dump code {
  685     display:inline;
  686     padding:0;
  687     background:none;
  688 }
  689 .sf-dump-str-collapse .sf-dump-str-collapse {
  690     display: none;
  691 }
  692 .sf-dump-str-expand .sf-dump-str-expand {
  693     display: none;
  694 }
  695 .sf-dump-public.sf-dump-highlight,
  696 .sf-dump-protected.sf-dump-highlight,
  697 .sf-dump-private.sf-dump-highlight,
  698 .sf-dump-str.sf-dump-highlight,
  699 .sf-dump-key.sf-dump-highlight {
  700     background: rgba(111, 172, 204, 0.3);
  701     border: 1px solid #7DA0B1;
  702     border-radius: 3px;
  703 }
  704 .sf-dump-public.sf-dump-highlight-active,
  705 .sf-dump-protected.sf-dump-highlight-active,
  706 .sf-dump-private.sf-dump-highlight-active,
  707 .sf-dump-str.sf-dump-highlight-active,
  708 .sf-dump-key.sf-dump-highlight-active {
  709     background: rgba(253, 175, 0, 0.4);
  710     border: 1px solid #ffa500;
  711     border-radius: 3px;
  712 }
  713 pre.sf-dump .sf-dump-search-hidden {
  714     display: none !important;
  715 }
  716 pre.sf-dump .sf-dump-search-wrapper {
  717     font-size: 0;
  718     white-space: nowrap;
  719     margin-bottom: 5px;
  720     display: flex;
  721     position: -webkit-sticky;
  722     position: sticky;
  723     top: 5px;
  724 }
  725 pre.sf-dump .sf-dump-search-wrapper > * {
  726     vertical-align: top;
  727     box-sizing: border-box;
  728     height: 21px;
  729     font-weight: normal;
  730     border-radius: 0;
  731     background: #FFF;
  732     color: #757575;
  733     border: 1px solid #BBB;
  734 }
  735 pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input {
  736     padding: 3px;
  737     height: 21px;
  738     font-size: 12px;
  739     border-right: none;
  740     border-top-left-radius: 3px;
  741     border-bottom-left-radius: 3px;
  742     color: #000;
  743     min-width: 15px;
  744     width: 100%;
  745 }
  746 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next,
  747 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous {
  748     background: #F2F2F2;
  749     outline: none;
  750     border-left: none;
  751     font-size: 0;
  752     line-height: 0;
  753 }
  754 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next {
  755     border-top-right-radius: 3px;
  756     border-bottom-right-radius: 3px;
  757 }
  758 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next > svg,
  759 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous > svg {
  760     pointer-events: none;
  761     width: 12px;
  762     height: 12px;
  763 }
  764 pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-count {
  765     display: inline-block;
  766     padding: 0 5px;
  767     margin: 0;
  768     border-left: none;
  769     line-height: 21px;
  770     font-size: 12px;
  771 }
  772 EOHTML
  773         );
  774 
  775         foreach ($this->styles as $class => $style) {
  776             $line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}';
  777         }
  778 
  779         return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
  780     }
  781 
  782     /**
  783      * {@inheritdoc}
  784      */
  785     public function enterHash(Cursor $cursor, $type, $class, $hasChild)
  786     {
  787         parent::enterHash($cursor, $type, $class, false);
  788 
  789         if ($cursor->skipChildren) {
  790             $cursor->skipChildren = false;
  791             $eol = ' class=sf-dump-compact>';
  792         } elseif ($this->expandNextHash) {
  793             $this->expandNextHash = false;
  794             $eol = ' class=sf-dump-expanded>';
  795         } else {
  796             $eol = '>';
  797         }
  798 
  799         if ($hasChild) {
  800             $this->line .= '<samp';
  801             if ($cursor->refIndex) {
  802                 $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2;
  803                 $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex;
  804 
  805                 $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r);
  806             }
  807             $this->line .= $eol;
  808             $this->dumpLine($cursor->depth);
  809         }
  810     }
  811 
  812     /**
  813      * {@inheritdoc}
  814      */
  815     public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
  816     {
  817         $this->dumpEllipsis($cursor, $hasChild, $cut);
  818         if ($hasChild) {
  819             $this->line .= '</samp>';
  820         }
  821         parent::leaveHash($cursor, $type, $class, $hasChild, 0);
  822     }
  823 
  824     /**
  825      * {@inheritdoc}
  826      */
  827     protected function style($style, $value, $attr = [])
  828     {
  829         if ('' === $value) {
  830             return '';
  831         }
  832 
  833         $v = esc($value);
  834 
  835         if ('ref' === $style) {
  836             if (empty($attr['count'])) {
  837                 return sprintf('<a class=sf-dump-ref>%s</a>', $v);
  838             }
  839             $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1);
  840 
  841             return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v);
  842         }
  843 
  844         if ('const' === $style && isset($attr['value'])) {
  845             $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
  846         } elseif ('public' === $style) {
  847             $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
  848         } elseif ('str' === $style && 1 < $attr['length']) {
  849             $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
  850         } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
  851             return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1));
  852         } elseif ('protected' === $style) {
  853             $style .= ' title="Protected property"';
  854         } elseif ('meta' === $style && isset($attr['title'])) {
  855             $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title'])));
  856         } elseif ('private' === $style) {
  857             $style .= sprintf(' title="Private property defined in class:&#10;`%s`"', esc($this->utf8Encode($attr['class'])));
  858         }
  859         $map = static::$controlCharsMap;
  860 
  861         if (isset($attr['ellipsis'])) {
  862             $class = 'sf-dump-ellipsis';
  863             if (isset($attr['ellipsis-type'])) {
  864                 $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']);
  865             }
  866             $label = esc(substr($value, -$attr['ellipsis']));
  867             $style = str_replace(' title="', " title=\"$v\n", $style);
  868             $v = sprintf('<span class=%s>%s</span>', $class, substr($v, 0, -\strlen($label)));
  869 
  870             if (!empty($attr['ellipsis-tail'])) {
  871                 $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail'])));
  872                 $v .= sprintf('<span class=sf-dump-ellipsis>%s</span>%s', substr($label, 0, $tail), substr($label, $tail));
  873             } else {
  874                 $v .= $label;
  875             }
  876         }
  877 
  878         $v = "<span class=sf-dump-{$style}>".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) {
  879             $s = $b = '<span class="sf-dump-default';
  880             $c = $c[$i = 0];
  881             if ($ns = "\r" === $c[$i] || "\n" === $c[$i]) {
  882                 $s .= ' sf-dump-ns';
  883             }
  884             $s .= '">';
  885             do {
  886                 if (("\r" === $c[$i] || "\n" === $c[$i]) !== $ns) {
  887                     $s .= '</span>'.$b;
  888                     if ($ns = !$ns) {
  889                         $s .= ' sf-dump-ns';
  890                     }
  891                     $s .= '">';
  892                 }
  893 
  894                 $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i]));
  895             } while (isset($c[++$i]));
  896 
  897             return $s.'</span>';
  898         }, $v).'</span>';
  899 
  900         if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
  901             $attr['href'] = $href;
  902         }
  903         if (isset($attr['href'])) {
  904             $target = isset($attr['file']) ? '' : ' target="_blank"';
  905             $v = sprintf('<a href="%s"%s rel="noopener noreferrer">%s</a>', esc($this->utf8Encode($attr['href'])), $target, $v);
  906         }
  907         if (isset($attr['lang'])) {
  908             $v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v);
  909         }
  910 
  911         return $v;
  912     }
  913 
  914     /**
  915      * {@inheritdoc}
  916      */
  917     protected function dumpLine($depth, $endOfValue = false)
  918     {
  919         if (-1 === $this->lastDepth) {
  920             $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
  921         }
  922         if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
  923             $this->line = $this->getDumpHeader().$this->line;
  924         }
  925 
  926         if (-1 === $depth) {
  927             $args = ['"'.$this->dumpId.'"'];
  928             if ($this->extraDisplayOptions) {
  929                 $args[] = json_encode($this->extraDisplayOptions, JSON_FORCE_OBJECT);
  930             }
  931             // Replace is for BC
  932             $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args));
  933         }
  934         $this->lastDepth = $depth;
  935 
  936         $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');
  937 
  938         if (-1 === $depth) {
  939             AbstractDumper::dumpLine(0);
  940         }
  941         AbstractDumper::dumpLine($depth);
  942     }
  943 
  944     private function getSourceLink($file, $line)
  945     {
  946         $options = $this->extraDisplayOptions + $this->displayOptions;
  947 
  948         if ($fmt = $options['fileLinkFormat']) {
  949             return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
  950         }
  951 
  952         return false;
  953     }
  954 }
  955 
  956 function esc($str)
  957 {
  958     return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
  959 }