diff --git a/library/vendor/Parsedown/.gitignore b/library/vendor/Parsedown/.gitignore deleted file mode 100644 index a7235d425..000000000 --- a/library/vendor/Parsedown/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.DS_Store -.idea -nbproject \ No newline at end of file diff --git a/library/vendor/Parsedown/Parsedown.php b/library/vendor/Parsedown/Parsedown.php index c848b4ee0..20db13475 100644 --- a/library/vendor/Parsedown/Parsedown.php +++ b/library/vendor/Parsedown/Parsedown.php @@ -8,1029 +8,1339 @@ # (c) Emanuil Rusev # http://erusev.com # -# For the full license information, please view the LICENSE file that was -# distributed with this source code. +# For the full license information, view the LICENSE file that was distributed +# with this source code. # # class Parsedown { - # - # Multiton (http://en.wikipedia.org/wiki/Multiton_pattern) - # + # + # Philosophy + + # Markdown is intended to be easy-to-read by humans - those of us who read + # line by line, left to right, top to bottom. In order to take advantage of + # this, Parsedown tries to read in a similar way. It breaks texts into + # lines, it iterates through them and it looks at how they start and relate + # to each other. + + # + # ~ + + function text($text) + { + # standardize line breaks + $text = str_replace("\r\n", "\n", $text); + $text = str_replace("\r", "\n", $text); + + # replace tabs with spaces + $text = str_replace("\t", ' ', $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + # clean up + $this->definitions = array(); + + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + private $breaksEnabled; + + # + # Blocks + # + + protected $blockMarkers = array( + '#' => array('Atx'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('Setext', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Markup'), + '=' => array('Setext'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + protected $definitionMarkers = array( + '[' => array('Reference'), + ); + + protected $unmarkedBlockTypes = array( + 'CodeBlock', + ); + + private function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + $indent = 0; - static function instance($name = 'default') - { - if (isset(self::$instances[$name])) - return self::$instances[$name]; + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # Multiline block types define "addTo" methods. + + if (isset($CurrentBlock['incomplete'])) + { + $Block = $this->{'addTo'.$CurrentBlock['type']}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if (method_exists($this, 'complete'.$CurrentBlock['type'])) + { + $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + } + + unset($CurrentBlock['incomplete']); + } + } + + # ~ + + $marker = $text[0]; + + # Definitions + + if (isset($this->definitionMarkers[$marker])) + { + foreach ($this->definitionMarkers[$marker] as $definitionType) + { + $Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock); + + if (isset($Definition)) + { + $this->definitions[$definitionType][$Definition['id']] = $Definition['data']; + + continue 2; + } + } + } + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; - $instance = new Parsedown(); + if (isset($this->blockMarkers[$marker])) + { + foreach ($this->blockMarkers[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } - self::$instances[$name] = $instance; + # + # ~ - return $instance; - } + foreach ($blockTypes as $blockType) + { + # Block types define "identify" methods. - private static $instances = array(); + $Block = $this->{'identify'.$blockType}($Line, $CurrentBlock); - # - # Setters - # + if (isset($Block)) + { + $Block['type'] = $blockType; - private $breaks_enabled = false; + if ( ! isset($Block['identified'])) # » + { + $Elements []= $CurrentBlock['element']; - function set_breaks_enabled($breaks_enabled) - { - $this->breaks_enabled = $breaks_enabled; + $Block['identified'] = true; + } - return $this; - } + # Multiline block types define "addTo" methods. - # - # Fields - # + if (method_exists($this, 'addTo'.$blockType)) + { + $Block['incomplete'] = true; + } - private $reference_map = array(); + $CurrentBlock = $Block; - # - # Public Methods - # + continue 2; + } + } - function parse($text) - { - # removes \r characters - $text = str_replace("\r\n", "\n", $text); - $text = str_replace("\r", "\n", $text); + # ~ - # replaces tabs with spaces - $text = str_replace("\t", ' ', $text); + if ($CurrentBlock['type'] === 'Paragraph' and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Elements []= $CurrentBlock['element']; + + $CurrentBlock = array( + 'type' => 'Paragraph', + 'identified' => true, + 'element' => array( + 'name' => 'p', + 'text' => $text, + 'handler' => 'line', + ), + ); + } + } + + # ~ + + if (isset($CurrentBlock['incomplete']) and method_exists($this, 'complete'.$CurrentBlock['type'])) + { + $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + } + + # ~ + + $Elements []= $CurrentBlock['element']; - # ~ + unset($Elements[0]); + + # ~ + + $markup = $this->elements($Elements); + + # ~ + + return $markup; + } + + # + # Atx + + protected function identifyAtx($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h'.$level, + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # Rule + + protected function identifyRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Reference + + protected function identifyReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $Definition = array( + 'id' => strtolower($matches[1]), + 'data' => array( + 'url' => $matches[2], + ), + ); + + if (isset($matches[3])) + { + $Definition['data']['title'] = $matches[3]; + } + + return $Definition; + } + } + + # + # Setext + + protected function identifySetext($Line, array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function identifyMarkup($Line) + { + if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>\/]*)?(\/?)[ ]*>/', $Line['text'], $matches)) + { + if (in_array($matches[1], $this->textLevelElements)) + { + return; + } + + $Block = array( + 'element' => $Line['body'], + ); + + if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text'])) + { + $Block['closed'] = true; + } + else + { + $Block['depth'] = 0; + $Block['start'] = '<'.$matches[1].'>'; + $Block['end'] = ''; + } + + return $Block; + } + } + + protected function addToMarkup($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (stripos($Line['text'], $Block['start']) !== false) # opening tag + { + $Block['depth'] ++; + } + + if (stripos($Line['text'], $Block['end']) !== false) # closing tag + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + $Block['element'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Fenced Code + + protected function identifyFencedCode($Line) + { + if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[2])) + { + $class = 'language-'.$matches[2]; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function addToFencedCode($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body'];; + + return $Block; + } + + protected function completeFencedCode($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # List + + protected function identifyList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function addToList($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'[ ]+(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + unset($Block['interrupted']); + } - $text = trim($text, "\n"); + unset($Block['li']); - $lines = explode("\n", $text); + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[1], + ), + ); - $text = $this->parse_block_elements($lines); + $Block['element']['text'] []= & $Block['li']; - $text = rtrim($text, "\n"); + return $Block; + } - return $text; - } + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); - # - # Private Methods - # + $Block['li']['text'] []= $text; - private function parse_block_elements(array $lines, $context = '') - { - $elements = array(); + return $Block; + } - $element = array( - 'type' => '', - ); + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; - foreach ($lines as $line) - { - # fenced elements + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); - switch ($element['type']) - { - case 'fenced block': + $Block['li']['text'] []= $text; - if ( ! isset($element['closed'])) - { - if (preg_match('/^[ ]*'.$element['fence'][0].'{3,}[ ]*$/', $line)) - { - $element['closed'] = true; - } - else - { - $element['text'] !== '' and $element['text'] .= "\n"; + unset($Block['interrupted']); - $element['text'] .= $line; - } + return $Block; + } + } - continue 2; - } + # + # Quote - break; + protected function identifyQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); - case 'block-level markup': + return $Block; + } + } - if ( ! isset($element['closed'])) - { - if (strpos($line, $element['start']) !== false) # opening tag - { - $element['depth']++; - } + protected function addToQuote($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; - if (strpos($line, $element['end']) !== false) # closing tag - { - $element['depth'] > 0 - ? $element['depth']-- - : $element['closed'] = true; - } + unset($Block['interrupted']); + } - $element['text'] .= "\n".$line; + $Block['element']['text'] []= $matches[1]; - continue 2; - } + return $Block; + } - break; - } + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; - # * + return $Block; + } + } - $deindented_line = ltrim($line); + # + # Table - if ($deindented_line === '') - { - $element['interrupted'] = true; + protected function identifyTable($Line, array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } - continue; - } + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); - # composite elements + $divider = $Line['text']; - switch ($element['type']) - { - case 'blockquote': + $divider = trim($divider); + $divider = trim($divider, '|'); - if ( ! isset($element['interrupted'])) - { - $line = preg_replace('/^[ ]*>[ ]?/', '', $line); + $dividerCells = explode('|', $divider); - $element['lines'] []= $line; + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); - continue 2; - } + if ($dividerCell === '') + { + continue; + } - break; + $alignment = null; - case 'li': + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } - if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches)) - { - if ($element['indentation'] !== $matches[1]) - { - $element['lines'] []= $line; - } - else - { - unset($element['last']); + if (substr($dividerCell, -1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } - $elements []= $element; + $alignments []= $alignment; + } - $element = array( - 'type' => 'li', - 'indentation' => $matches[1], - 'last' => true, - 'lines' => array( - preg_replace('/^[ ]{0,4}/', '', $matches[3]), - ), - ); - } + # ~ - continue 2; - } + $HeaderElements = array(); - if (isset($element['interrupted'])) - { - if ($line[0] === ' ') - { - $element['lines'] []= ''; + $header = $Block['element']['text']; - $line = preg_replace('/^[ ]{0,4}/', '', $line); + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'align' => $alignment, + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); - $element['lines'] []= $line; + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); - unset($element['interrupted']); + return $Block; + } + } - continue 2; - } - } - else - { - $line = preg_replace('/^[ ]{0,4}/', '', $line); + protected function addToTable($Line, array $Block) + { + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); - $element['lines'] []= $line; + $row = $Line['text']; - continue 2; - } + $row = trim($row); + $row = trim($row, '|'); - break; - } + $cells = explode('|', $row); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'align' => $Block['alignments'][$index], + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # Code + + protected function identifyCodeBlock($Line) + { + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function addToCodeBlock($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function completeCodeBlock($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # ~ + # + + private function element(array $Element) + { + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + $markup .= ' '.$name.'="'.$value.'"'; + } + } + + if (isset($Element['text'])) + { + $markup .= '>'; + + if (isset($Element['handler'])) + { + $markup .= $this->$Element['handler']($Element['text']); + } + else + { + $markup .= $Element['text']; + } - # indentation sensitive types + $markup .= ''; + } + else + { + $markup .= ' />'; + } - switch ($line[0]) - { - case ' ': + return $markup; + } - # code block + private function elements(array $Elements) + { + $markup = ''; - if (isset($line[3]) and $line[3] === ' ' and $line[2] === ' ' and $line[1] === ' ') - { - $code_line = substr($line, 4); + foreach ($Elements as $Element) + { + if ($Element === null) + { + continue; + } - if ($element['type'] === 'code block') - { - if (isset($element['interrupted'])) - { - $element['text'] .= "\n"; + $markup .= "\n"; - unset ($element['interrupted']); - } + if (is_string($Element)) # because of markup + { + $markup .= $Element; - $element['text'] .= "\n".$code_line; - } - else - { - $elements []= $element; + continue; + } - $element = array( - 'type' => 'code block', - 'text' => $code_line, - ); - } + $markup .= $this->element($Element); + } - continue 2; - } + $markup .= "\n"; - break; + return $markup; + } - case '#': + # + # Spans + # - # atx heading (#) + protected $spanMarkers = array( + '!' => array('Link'), # ? + '&' => array('Ampersand'), + '*' => array('Emphasis'), + '/' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Tag', 'LessThan'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('InlineCode'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); - if (isset($line[1])) - { - $elements []= $element; + protected $spanMarkerList = '*_!&[ 'heading', - 'text' => trim($line, '# '), - 'level' => $level, - ); - - continue 2; - } - - break; - - case '-': - case '=': - - # setext heading - - if ($element['type'] === 'paragraph' and isset($element['interrupted']) === false) - { - $chopped_line = rtrim($line); - - $i = 1; - - while (isset($chopped_line[$i])) - { - if ($chopped_line[$i] !== $line[0]) - { - break 2; - } - - $i++; - } - - $element['type'] = 'heading'; - $element['level'] = $line[0] === '-' ? 2 : 1; - - continue 2; - } - - break; - } - - # indentation insensitive types - - switch ($deindented_line[0]) - { - case '<': - - $position = strpos($deindented_line, '>'); - - if ($position > 1) # tag - { - $name = substr($deindented_line, 1, $position - 1); - $name = rtrim($name); - - if (substr($name, -1) === '/') - { - $self_closing = true; - - $name = substr($name, 0, -1); - } - - $position = strpos($name, ' '); - - if ($position) - { - $name = substr($name, 0, $position); - } - - if ( ! ctype_alpha($name)) - { - break; - } - - if (in_array($name, $this->inline_tags)) - { - break; - } - - $elements []= $element; - - if (isset($self_closing)) - { - $element = array( - 'type' => 'self-closing tag', - 'text' => $deindented_line, - ); - - unset($self_closing); - - continue 2; - } - - $element = array( - 'type' => 'block-level markup', - 'text' => $deindented_line, - 'start' => '<'.$name.'>', - 'end' => '', - 'depth' => 0, - ); - - if (strpos($deindented_line, $element['end'])) - { - $element['closed'] = true; - } - - continue 2; - } - - break; - - case '>': - - # quote - - if (preg_match('/^>[ ]?(.*)/', $deindented_line, $matches)) - { - $elements []= $element; - - $element = array( - 'type' => 'blockquote', - 'lines' => array( - $matches[1], - ), - ); - - continue 2; - } - - break; - - case '[': - - # reference - - if (preg_match('/^\[(.+?)\]:[ ]*(.+?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*$/', $deindented_line, $matches)) - { - $label = strtolower($matches[1]); - - $this->reference_map[$label] = array( - '»' => trim($matches[2], '<>'), - ); - - if (isset($matches[3])) - { - $this->reference_map[$label]['#'] = $matches[3]; - } - - continue 2; - } - - break; - - case '`': - case '~': - - # fenced code block - - if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\S+)?[ ]*$/', $deindented_line, $matches)) - { - $elements []= $element; - - $element = array( - 'type' => 'fenced block', - 'text' => '', - 'fence' => $matches[1], - ); - - isset($matches[2]) and $element['language'] = $matches[2]; - - continue 2; - } - - break; - - case '*': - case '+': - case '-': - case '_': - - # hr - - if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $deindented_line)) - { - $elements []= $element; - - $element = array( - 'type' => 'hr', - ); - - continue 2; - } - - # li - - if (preg_match('/^([ ]*)[*+-][ ](.*)/', $line, $matches)) - { - $elements []= $element; - - $element = array( - 'type' => 'li', - 'ordered' => false, - 'indentation' => $matches[1], - 'last' => true, - 'lines' => array( - preg_replace('/^[ ]{0,4}/', '', $matches[2]), - ), - ); - - continue 2; - } - } - - # li - - if ($deindented_line[0] <= '9' and $deindented_line[0] >= '0' and preg_match('/^([ ]*)\d+[.][ ](.*)/', $line, $matches)) - { - $elements []= $element; - - $element = array( - 'type' => 'li', - 'ordered' => true, - 'indentation' => $matches[1], - 'last' => true, - 'lines' => array( - preg_replace('/^[ ]{0,4}/', '', $matches[2]), - ), - ); - - continue; - } - - # paragraph - - if ($element['type'] === 'paragraph') - { - if (isset($element['interrupted'])) - { - $elements []= $element; - - $element['text'] = $line; - - unset($element['interrupted']); - } - else - { - $this->breaks_enabled and $element['text'] .= ' '; - - $element['text'] .= "\n".$line; - } - } - else - { - $elements []= $element; - - $element = array( - 'type' => 'paragraph', - 'text' => $line, - ); - } - } - - $elements []= $element; - - unset($elements[0]); - - # - # ~ - # - - $markup = ''; - - foreach ($elements as $element) - { - switch ($element['type']) - { - case 'paragraph': - - $text = $this->parse_span_elements($element['text']); - - if ($context === 'li' and $markup === '') - { - if (isset($element['interrupted'])) - { - $markup .= "\n".'

'.$text.'

'."\n"; - } - else - { - $markup .= $text; - } - } - else - { - $markup .= '

'.$text.'

'."\n"; - } - - break; - - case 'blockquote': - - $text = $this->parse_block_elements($element['lines']); - - $markup .= '
'."\n".$text.'
'."\n"; - - break; - - case 'code block': - - $text = htmlspecialchars($element['text'], ENT_NOQUOTES, 'UTF-8'); - - $markup .= '
'.$text.'
'."\n"; - - break; - - case 'fenced block': - - $text = htmlspecialchars($element['text'], ENT_NOQUOTES, 'UTF-8'); - - $markup .= '
'."\n"; - - break; - - case 'heading': - - $text = $this->parse_span_elements($element['text']); - - $markup .= ''.$text.''."\n"; - - break; - - case 'hr': - - $markup .= '
'."\n"; - - break; - - case 'li': - - if (isset($element['ordered'])) # first - { - $list_type = $element['ordered'] ? 'ol' : 'ul'; - - $markup .= '<'.$list_type.'>'."\n"; - } - - if (isset($element['interrupted']) and ! isset($element['last'])) - { - $element['lines'] []= ''; - } - - $text = $this->parse_block_elements($element['lines'], 'li'); - - $markup .= '
  • '.$text.'
  • '."\n"; - - isset($element['last']) and $markup .= ''."\n"; - - break; - - case 'block-level markup': - - $markup .= $element['text']."\n"; - - break; - - default: - - $markup .= $element['text']."\n"; - } - } - - return $markup; - } - - private function parse_span_elements($text, $markers = array(" \n", '![', '&', '*', '<', '[', '\\', '_', '`', 'http', '~~')) - { - if (isset($text[1]) === false or $markers === array()) - { - return $text; - } - - # ~ - - $markup = ''; - - while ($markers) - { - $closest_marker = null; - $closest_marker_index = 0; - $closest_marker_position = null; - - foreach ($markers as $index => $marker) - { - $marker_position = strpos($text, $marker); - - if ($marker_position === false) - { - unset($markers[$index]); - - continue; - } - - if ($closest_marker === null or $marker_position < $closest_marker_position) - { - $closest_marker = $marker; - $closest_marker_index = $index; - $closest_marker_position = $marker_position; - } - } - - # ~ - - if ($closest_marker === null or isset($text[$closest_marker_position + 1]) === false) - { - $markup .= $text; - - break; - } - else - { - $markup .= substr($text, 0, $closest_marker_position); - } - - $text = substr($text, $closest_marker_position); - - # ~ - - unset($markers[$closest_marker_index]); - - # ~ - - switch ($closest_marker) - { - case " \n": - - $markup .= '
    '."\n"; - - $offset = 3; - - break; - - case '![': - case '[': - - if (strpos($text, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $text, $matches)) - { - $element = array( - '!' => $text[0] === '!', - 'a' => $matches[1], - ); - - $offset = strlen($matches[0]); - - $element['!'] and $offset++; - - $remaining_text = substr($text, $offset); - - if ($remaining_text[0] === '(' and preg_match('/\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $remaining_text, $matches)) - { - $element['»'] = $matches[1]; - - if (isset($matches[2])) - { - $element['#'] = $matches[2]; - } - - $offset += strlen($matches[0]); - } - elseif ($this->reference_map) - { - $reference = $element['a']; - - if (preg_match('/^\s*\[(.*?)\]/', $remaining_text, $matches)) - { - $reference = $matches[1] ? $matches[1] : $element['a']; - - $offset += strlen($matches[0]); - } - - $reference = strtolower($reference); - - if (isset($this->reference_map[$reference])) - { - $element['»'] = $this->reference_map[$reference]['»']; - - if (isset($this->reference_map[$reference]['#'])) - { - $element['#'] = $this->reference_map[$reference]['#']; - } - } - else - { - unset($element); - } - } - else - { - unset($element); - } - } - - if (isset($element)) - { - $element['»'] = str_replace('&', '&', $element['»']); - $element['»'] = str_replace('<', '<', $element['»']); - // TODO: this should be documented and patched upstream - $element['»'] = preg_replace('~ =(\d+)x$~', '" width="$1', $element['»']); - $element['»'] = preg_replace('~ =(\d+)x(\d+)$~', '" width="$1" height="$2"', $element['»']); - - if ($element['!']) - { - $markup .= ''.$element['a'].''; - } - else - { - $element['a'] = $this->parse_span_elements($element['a'], $markers); - - $markup .= isset($element['#']) - ? ''.$element['a'].'' - : ''.$element['a'].''; - } - - unset($element); - } - else - { - $markup .= $closest_marker; - - $offset = $closest_marker === '![' ? 2 : 1; - } - - break; - - case '&': - - if (preg_match('/^&#?\w+;/', $text, $matches)) - { - $markup .= $matches[0]; - - $offset = strlen($matches[0]); - } - else - { - $markup .= '&'; - - $offset = 1; - } - - break; - - case '*': - case '_': - - if ($text[1] === $closest_marker and preg_match($this->strong_regex[$closest_marker], $text, $matches)) - { - $matches[1] = $this->parse_span_elements($matches[1], $markers); - - $markup .= ''.$matches[1].''; - } - elseif (preg_match($this->em_regex[$closest_marker], $text, $matches)) - { - $matches[1] = $this->parse_span_elements($matches[1], $markers); - - $markup .= ''.$matches[1].''; - } - elseif ($text[1] === $closest_marker and preg_match($this->strong_em_regex[$closest_marker], $text, $matches)) - { - $matches[2] = $this->parse_span_elements($matches[2], $markers); - - $matches[1] and $matches[1] = $this->parse_span_elements($matches[1], $markers); - $matches[3] and $matches[3] = $this->parse_span_elements($matches[3], $markers); - - $markup .= ''.$matches[1].''.$matches[2].''.$matches[3].''; - } - elseif (preg_match($this->em_strong_regex[$closest_marker], $text, $matches)) - { - $matches[2] = $this->parse_span_elements($matches[2], $markers); - - $matches[1] and $matches[1] = $this->parse_span_elements($matches[1], $markers); - $matches[3] and $matches[3] = $this->parse_span_elements($matches[3], $markers); - - $markup .= ''.$matches[1].''.$matches[2].''.$matches[3].''; - } - - if (isset($matches) and $matches) - { - $offset = strlen($matches[0]); - } - else - { - $markup .= $closest_marker; - - $offset = 1; - } - - break; - - case '<': - - if (strpos($text, '>') !== false) - { - if ($text[1] === 'h' and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $text, $matches)) - { - $element_url = $matches[1]; - $element_url = str_replace('&', '&', $element_url); - $element_url = str_replace('<', '<', $element_url); - - $markup .= ''.$element_url.''; - - $offset = strlen($matches[0]); - } - elseif (preg_match('/^<\/?\w.*?>/', $text, $matches)) - { - $markup .= $matches[0]; - - $offset = strlen($matches[0]); - } - else - { - $markup .= '<'; - - $offset = 1; - } - } - else - { - $markup .= '<'; - - $offset = 1; - } - - break; - - case '\\': - - if (in_array($text[1], $this->special_characters)) - { - $markup .= $text[1]; - - $offset = 2; - } - else - { - $markup .= '\\'; - - $offset = 1; - } - - break; - - case '`': - - if (preg_match('/^`(.+?)`/', $text, $matches)) - { - $element_text = $matches[1]; - $element_text = htmlspecialchars($element_text, ENT_NOQUOTES, 'UTF-8'); - - $markup .= ''.$element_text.''; - - $offset = strlen($matches[0]); - } - else - { - $markup .= '`'; - - $offset = 1; - } - - break; - - case 'http': - - if (preg_match('/^https?:[\/]{2}[^\s]+\b/i', $text, $matches)) - { - $element_url = $matches[0]; - $element_url = str_replace('&', '&', $element_url); - $element_url = str_replace('<', '<', $element_url); - - $markup .= ''.$element_url.''; - - $offset = strlen($matches[0]); - } - else - { - $markup .= 'http'; - - $offset = 4; - } - - break; - - case '~~': - - if (preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $text, $matches)) - { - $matches[1] = $this->parse_span_elements($matches[1], $markers); - - $markup .= ''.$matches[1].''; - - $offset = strlen($matches[0]); - } - else - { - $markup .= '~~'; - - $offset = 2; - } - - break; - } - - if (isset($offset)) - { - $text = substr($text, $offset); - } - - $markers[$closest_marker_index] = $closest_marker; - } - - return $markup; - } - - # - # Read-only - # - - private $inline_tags = array( - 'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', - 'cite', 'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', - 'label', 'map', 'object', 'q', 'samp', 'script', 'select', 'small', - 'span', 'strong', 'sub', 'sup', 'textarea', 'tt', 'var', - ); - - private $special_characters = array('\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!'); - - # ~ - - private $strong_regex = array( - '*' => '/^[*]{2}([^*]+?)[*]{2}(?![*])/s', - '_' => '/^__([^_]+?)__(?!_)/s', - ); - - private $em_regex = array( - '*' => '/^[*]([^*]+?)[*](?![*])/s', - '_' => '/^_([^_]+?)[_](?![_])\b/s', - ); - - private $strong_em_regex = array( - '*' => '/^[*]{2}(.*?)[*](.+?)[*](.*?)[*]{2}/s', - '_' => '/^__(.*?)_(.+?)_(.*?)__/s', - ); - - private $em_strong_regex = array( - '*' => '/^[*](.*?)[*]{2}(.+?)[*]{2}(.*?)[*]/s', - '_' => '/^_(.*?)__(.+?)__(.*?)_/s', - ); -} \ No newline at end of file + $markerPosition = 0; + + while ($markedExcerpt = strpbrk($remainder, $this->spanMarkerList)) + { + $marker = $markedExcerpt[0]; + + $markerPosition += strpos($remainder, $marker); + + foreach ($this->spanMarkers[$marker] as $spanType) + { + $handler = 'identify'.$spanType; + + $Span = $this->$handler($markedExcerpt, $text); + + if ( ! isset($Span)) + { + continue; + } + + # The identified span can be ahead of the marker. + + if (isset($Span['position']) and $Span['position'] > $markerPosition) + { + continue; + } + + # Spans that start at the position of their marker don't have to set a position. + + if ( ! isset($Span['position'])) + { + $Span['position'] = $markerPosition; + } + + $plainText = substr($text, 0, $Span['position']); + + $markup .= $this->readPlainText($plainText); + + $markup .= isset($Span['element']) ? $this->element($Span['element']) : $Span['markup']; + + $text = substr($text, $Span['position'] + $Span['extent']); + + $remainder = $text; + + $markerPosition = 0; + + continue 2; + } + + $remainder = substr($markedExcerpt, 1); + + $markerPosition ++; + } + + $markup .= $this->readPlainText($text); + + return $markup; + } + + # + # ~ + # + + protected function identifyUrl($excerpt, $text) + { + if ( ! isset($excerpt[1]) or $excerpt[1] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s]+\b\/*/ui', $text, $matches, PREG_OFFSET_CAPTURE)) + { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[0][0]); + + return array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function identifyAmpersand($excerpt) + { + if ( ! preg_match('/^&#?\w+;/', $excerpt)) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + } + + protected function identifyStrikethrough($excerpt) + { + if ( ! isset($excerpt[1])) + { + return; + } + + if ($excerpt[1] === $excerpt[0] and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $excerpt, $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function identifyEscapeSequence($excerpt) + { + if (in_array($excerpt[1], $this->specialCharacters)) + { + return array( + 'markup' => $excerpt[1], + 'extent' => 2, + ); + } + } + + protected function identifyLessThan() + { + return array( + 'markup' => '<', + 'extent' => 1, + ); + } + + protected function identifyUrlTag($excerpt) + { + if (strpos($excerpt, '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $excerpt, $matches)) + { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function identifyEmailTag($excerpt) + { + if (strpos($excerpt, '>') !== false and preg_match('/^<(\S+?@\S+?)>/', $excerpt, $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => 'mailto:'.$matches[1], + ), + ), + ); + } + } + + protected function identifyTag($excerpt) + { + if (strpos($excerpt, '>') !== false and preg_match('/^<\/?\w.*?>/', $excerpt, $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function identifyInlineCode($excerpt) + { + $marker = $excerpt[0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function identifyLink($excerpt) + { + $extent = $excerpt[0] === '!' ? 1 : 0; + + if (strpos($excerpt, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $excerpt, $matches)) + { + $Link = array('text' => $matches[1], 'label' => strtolower($matches[1])); + + $extent += strlen($matches[0]); + + $substring = substr($excerpt, $extent); + + if (preg_match('/^\s*\[(.+?)\]/', $substring, $matches)) + { + $Link['label'] = strtolower($matches[1]); + + if (isset($this->definitions['Reference'][$Link['label']])) + { + $Link += $this->definitions['Reference'][$Link['label']]; + + $extent += strlen($matches[0]); + } + else + { + return; + } + } + elseif (isset($this->definitions['Reference'][$Link['label']])) + { + $Link += $this->definitions['Reference'][$Link['label']]; + + if (preg_match('/^[ ]*\[\]/', $substring, $matches)) + { + $extent += strlen($matches[0]); + } + } + elseif (preg_match('/^\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $substring, $matches)) + { + $Link['url'] = $matches[1]; + + if (isset($matches[2])) + { + $Link['title'] = $matches[2]; + } + + $extent += strlen($matches[0]); + } + else + { + return; + } + } + else + { + return; + } + + $url = str_replace(array('&', '<'), array('&', '<'), $Link['url']); + + if ($excerpt[0] === '!') + { + $Element = array( + 'name' => 'img', + 'attributes' => array( + 'alt' => $Link['text'], + 'src' => $url, + ), + ); + } + else + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'text' => $Link['text'], + 'attributes' => array( + 'href' => $url, + ), + ); + } + + if (isset($Link['title'])) + { + $Element['attributes']['title'] = $Link['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function identifyEmphasis($excerpt) + { + if ( ! isset($excerpt[1])) + { + return; + } + + $marker = $excerpt[0]; + + if ($excerpt[1] === $marker and preg_match($this->strongRegex[$marker], $excerpt, $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->emRegex[$marker], $excerpt, $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + # + # ~ + + protected function readPlainText($text) + { + $breakMarker = $this->breaksEnabled ? "\n" : " \n"; + + $text = str_replace($breakMarker, "
    \n", $text); + + return $text; + } + + # + # ~ + # + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

    ') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

    "); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Multiton + # + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new self(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Deprecated Methods + # + + /** + * @deprecated in favor of "text" + */ + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + # + # Fields + # + + protected $definitions; + + # + # Read-only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', + ); + + protected $strongRegex = array( + '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $emRegex = array( + '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'sub', 'mark', + 'u', 'xm', 'sup', 'nobr', + 'var', 'ruby', + 'wbr', 'span', + 'time', + ); +} diff --git a/library/vendor/Parsedown/README.md b/library/vendor/Parsedown/README.md deleted file mode 100644 index 29f77f59c..000000000 --- a/library/vendor/Parsedown/README.md +++ /dev/null @@ -1,31 +0,0 @@ -## Parsedown - -Better [Markdown](http://en.wikipedia.org/wiki/Markdown) parser for PHP. - -*** - -[ [demo](http://parsedown.org/demo) ] [ [tests](http://parsedown.org/tests/) ] - -*** - -### Features - -* [fast](http://parsedown.org/speed) -* [consistent](http://parsedown.org/consistency) -* [GitHub Flavored](https://help.github.com/articles/github-flavored-markdown) -* friendly to international input -* [tested](https://travis-ci.org/erusev/parsedown) in PHP 5.2, 5.3, 5.4, 5.5 and [hhvm](http://www.hhvm.com/) - -### Installation - -Include `Parsedown.php` or install [the composer package](https://packagist.org/packages/erusev/parsedown). - -### Example - -```php -$text = 'Hello _Parsedown_!'; - -$result = Parsedown::instance()->parse($text); - -echo $result; # prints:

    Hello Parsedown!

    -``` diff --git a/library/vendor/Parsedown/SOURCE b/library/vendor/Parsedown/SOURCE new file mode 100644 index 000000000..59f79409d --- /dev/null +++ b/library/vendor/Parsedown/SOURCE @@ -0,0 +1,6 @@ +RELEASE=1.0.0-rc.4 +FILENAME=parsedown-$RELEASE +DESTINATION=. +wget -O ${FILENAME}.tar.gz https://github.com/erusev/parsedown/archive/${RELEASE}.tar.gz +tar xfz ${FILENAME}.tar.gz -C $DESTINATION/ --strip-components 1 $FILENAME/Parsedown.php $FILENAME/LICENSE.txt +chmod 644 $DESTINATION/Parsedown.php diff --git a/library/vendor/Parsedown/VERSION.txt b/library/vendor/Parsedown/VERSION.txt deleted file mode 100644 index ee94dd834..000000000 --- a/library/vendor/Parsedown/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -0.8.3