/** * @param array $matches A set of results of the `transform` function * @return string */ protected function _email_callback($matches) { $address = $matches[1]; Kernel::addConfig('urls', $address); $block = Kernel::get('OutputFormatBag')->buildTag('link', $address, array('email' => $address)); return parent::hashPart($block); }
/** * Sets a collection entry * * @param string $index * @param string $content * * @throws \MarkdownExtended\Exception\UnexpectedValueException it the argument does not implement `\MarkdownExtended\API\ContentInterface` */ public function offsetSet($index, $content) { if (!is_object($content) || !Kernel::valid($content, Kernel::TYPE_CONTENT)) { throw new UnexpectedValueException(sprintf('Method "%s" expects the second parameter to implement "%s", got "%s"', __METHOD__, Kernel::CONTENT_INTERFACE, is_object($content) ? get_class($content) : gettype($content))); } parent::offsetSet($index, $content); }
/** * Gets a template file content * * @param string $template_path * * @return mixed|string * * @throws \MarkdownExtended\Exception\FileSystemException if the template can not be found or is not readable */ public function getTemplate($template_path) { if (true === $template_path) { $template_path = Kernel::getConfig('output_format_options.' . Kernel::getConfig('output_format') . '.default_template'); if (empty($template_path)) { return Kernel::getConfig('template_options.inline_template'); } } if (!file_exists($template_path)) { $local_path = Kernel::getResourcePath($template_path, Kernel::RESOURCE_TEMPLATE); if (empty($local_path) || !file_exists($local_path)) { throw new FileSystemException(sprintf('Template "%s" not found', $template_path)); } $template_path = $local_path; } if (!$this->cache->isCached($template_path)) { if (!is_readable($template_path)) { throw new FileSystemException(sprintf('Template "%s" is not readable', $template_path)); } $tpl_content = Helper::readFile($template_path); $this->cache->setCache($template_path, $tpl_content); } else { $tpl_content = $this->cache->getCache($template_path); } return $tpl_content; }
/** * Process each inclusion, errors are written as comments * * @param array $matches One set of results form the `transform()` function * @return string The result of the inclusion parsed if so */ protected function _callback($matches) { $filename = $matches[1]; if (!file_exists($filename)) { $base_path = Kernel::getConfig('base_path'); if (!is_array($base_path)) { $base_path = array($base_path); } foreach ($base_path as $path) { $file = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; if (file_exists($file)) { $filename = $file; break; } } } $content_collection = Kernel::get('ContentCollection'); $index = $content_collection->key(); try { $parsed_content = Kernel::get('Parser')->transformSource($filename, false); } catch (\Exception $e) { $parsed_content = Kernel::get('OutputFormatBag')->buildTag('comment', "ERROR while parsing {$filename} : '{$e->getMessage()}'"); } Kernel::get('ContentCollection')->seek($index); return $parsed_content; }
/** * @param string $text * @return string */ public function transform($text) { return preg_replace('{ ^[ ]{0,3} # Leading space ([-*_]) # $1: First marker (?> # Repeated marker group [ ]{0,2} # Zero, one, or two spaces. \\1 # Marker character ){2,} # Group repeated at least twice [ ]* # Tailing spaces $ # End of line. }mx', "\n" . parent::hashBlock(Kernel::get('OutputFormatBag')->buildTag('horizontal_rule')) . "\n", $text); }
/** * Loads a new formatter * * @param string $format The formatter name * @throws \MarkdownExtended\Exception\InvalidArgumentException if the class can not be found */ public function load($format) { $cls_name = $format; if (!class_exists($cls_name)) { $cls_name = '\\MarkdownExtended\\OutputFormat\\' . Helper::toCamelCase($format); } if (!class_exists($cls_name)) { throw new InvalidArgumentException(sprintf('Output format "%s" not found', $format)); } $cls = new $cls_name(); if (Kernel::validate($cls, Kernel::TYPE_OUTPUTFORMAT, $format)) { $this->setFormatter($cls); } }
/** * Take the string $str and parse it into tokens, hashing embedded HTML, * escaped characters and handling code and maths spans. * * @param string $str * @return string */ public function transform($str) { $output = ''; $span_re = '{ ( \\\\' . Kernel::getConfig('escaped_characters_re') . ' | (?<![`\\\\]) `+ # code span marker | \\ \\( # inline math ' . (Kernel::getConfig('no_markup') === true ? '' : ' | <!-- .*? --> # comment | <\\?.*?\\?> | <%.*?%> # processing instruction | <[/!$]?[-a-zA-Z0-9:_]+ # regular tags (?> \\s (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* )? > ') . ' ) }xs'; while (1) { // Each loop iteration search for either the next tag, the next // opening code span marker, or the next escaped character. // Each token is then passed to handleSpanToken. $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); // Create token from text preceding tag. if ($parts[0] !== '') { $output .= $parts[0]; } // Check if we reach the end. if (isset($parts[1])) { $output .= self::handleSpanToken($parts[1], $parts[2]); $str = $parts[2]; } else { break; } } return $output; }
/** * Process tabs replacement * * @param array $matches A set of results of the `detab()` function * @return string The line rebuilt */ protected function _callback($matches) { $line = $matches[0]; $strlen = $this->utf8_strlen; // strlen function for UTF-8. // Split in blocks. $blocks = explode("\t", $line); // Add each blocks to the line. $line = $blocks[0]; unset($blocks[0]); // Do not add first block twice. foreach ($blocks as $block) { // Calculate amount of space, insert spaces, insert block. $amount = Kernel::getConfig('tab_width') - $strlen($line, 'UTF-8') % Kernel::getConfig('tab_width'); $line .= str_repeat(" ", $amount) . $block; } return $line; }
/** * Build each blockquote block * * @param array $matches A set of results of the `transform()` function * @return string */ protected function _callback($matches) { $blockq = $matches[1]; $cite = isset($matches[2]) ? $matches[2] : null; // trim one level of quoting - trim whitespace-only lines $blockq = preg_replace('/^[ ]*>[ ]?(\\((.+?)\\))?|^[ ]+$/m', '', $blockq); $blockq = Lexer::runGamut('html_block_gamut', $blockq); # recurse $blockq = preg_replace('/^/m', " ", $blockq); // These leading spaces cause problem with <pre> content, // so we need to fix that: $blockq = preg_replace_callback('{(\\s*<pre>.+?</pre>)}sx', array($this, '_callback_spaces'), $blockq); $attributes = array(); if (!empty($cite)) { $attributes['cite'] = $cite; } $block = Kernel::get('OutputFormatBag')->buildTag('blockquote', $blockq, $attributes); return "\n" . parent::hashBlock($block) . "\n\n"; }
/** * @param array $matches A set of results of the `transform` function * @return string */ protected function _inline_callback($matches) { $alt_text = $matches[2]; $url = $matches[3] == '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $attributes = array(); $attributes['alt'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $alt_text); $attributes['src'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $url); if (!empty($title)) { $attributes['title'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $title); } $block = Kernel::get('OutputFormatBag')->buildTag('image', null, $attributes); return parent::hashPart($block); }
/** * Run a gamut stack from a filter or tool * * @param string $gamut The name of a single Gamut or a Gamuts stack * @param string $text * @param bool $forced Forces to run the gamut event if it is disabled * * @return string */ public function runGamut($gamut, $text, $forced = false) { $loader = Kernel::get('GamutLoader'); return $loader->isGamutEnabled($gamut) || $forced ? $loader->runGamut($gamut, $text) : $text; }
/** * Process the dd contents. * * @param array $matches * @return string */ protected function _item_callback_dd($matches) { $leading_line = $matches[1]; $marker_space = $matches[2]; $def = $matches[3]; if ($leading_line || preg_match('/\\n{2,}/', $def)) { // Replace marker with the appropriate whitespace indentation $def = str_repeat(' ', strlen($marker_space)) . $def; $def = Lexer::runGamut('html_block_gamut', Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':Outdent', $def . "\n\n")); // $def = "\n$def\n"; } else { $def = rtrim($def); $def = Lexer::runGamut('span_gamut', Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':Outdent', $def)); } return Kernel::get('OutputFormatBag')->buildTag('definition_list_item_definition', $def); }
public function buildFootnoteStandardLink($text = null, array $attributes = array(), $note_type = Note::FOOTNOTE_DEFAULT) { $type_info = Note::getTypeInfo($note_type); if ($this->getConfig($type_info['prefix'] . '_link_class')) { $attributes['class'] = Helper::fillPlaceholders(Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $this->getConfig($type_info['prefix'] . '_link_class')), $text); } if ($this->getConfig($type_info['prefix'] . '_link_title_mask')) { $attributes['title'] = Helper::fillPlaceholders(Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $this->getConfig($type_info['prefix'] . '_link_title_mask')), $text); } $backlink_id = $attributes['backlink_id']; unset($attributes['backlink_id']); unset($attributes['counter']); $link = Kernel::get('OutputFormatBag')->buildTag('link', $text, $attributes); return Kernel::get('OutputFormatBag')->buildTag('sup', $link, array('id' => $backlink_id)); }
/** * @param array $matches A set of results of the `transform()` function * @return string */ protected function _items_callback($matches) { $item = $matches[4]; $leading_line =& $matches[1]; $leading_space =& $matches[2]; $marker_space = $matches[3]; $trailing_blank_line =& $matches[5]; if ($leading_line || $trailing_blank_line || preg_match('/\\n{2,}/', $item)) { // Replace marker with the appropriate whitespace indentation $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; $item = Lexer::runGamut('html_block_gamut', Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':Outdent', $item) . "\n"); } else { // Recursion for sub-lists: $item = self::transform(Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':Outdent', $item)); $item = preg_replace('/\\n+$/', '', $item); $item = Lexer::runGamut('span_gamut', $item); } return Kernel::get('OutputFormatBag')->buildTag('list_item', $item); }
/** * @param array $matches A set of results of the `transform()` function * @return string */ protected function _callback($matches) { return parent::hashPart(Kernel::get('OutputFormatBag')->buildTag('new_line') . "\n"); }
/** * Append footnote and glossary list to text. * * @param array $matches * @return string */ protected function _append_callback($matches) { $note_id = $matches[1]; $note_num = $note_ref = null; // Create footnote marker only if it has a corresponding footnote *and* // the footnote hasn't been used by another marker. $node_id = Kernel::getConfig('footnote_id_prefix') . $note_id; $footnotes = Kernel::getConfig('footnotes'); if (isset($footnotes[$node_id])) { $type_info = $this->getTypeInfo(self::FOOTNOTE_DEFAULT); // Transfer footnote content to the ordered list. self::$notes_ordered[$node_id] = $footnotes[$node_id]; $note_num = array_key_exists($node_id, self::$written_notes) ? self::$written_notes[$node_id] : self::$footnote_counter++; $note_ref = $node_id; } // Create glossary marker only if it has a corresponding note *and* // the glossary hasn't been used by another marker. $glossary_node_id = Kernel::getConfig('glossarynote_id_prefix') . $note_id; $glossaries = Kernel::getConfig('glossaries'); if (isset($glossaries[$glossary_node_id])) { $type_info = $this->getTypeInfo(self::FOOTNOTE_GLOSSARY); // Transfer footnote content to the ordered list. self::$notes_ordered[$glossary_node_id] = $glossaries[$glossary_node_id]; $note_num = array_key_exists($note_id, self::$written_notes) ? self::$written_notes[$note_id] : self::$footnote_counter++; $note_ref = $glossary_node_id; } // Create bibliography marker only if it has a corresponding note *and* // the glossary hasn't been used by another marker. $bibliography_node_id = Kernel::getConfig('bibliographynote_id_prefix') . $note_id; $bibliographies = Kernel::getConfig('bibliographies'); if (isset($bibliographies[$bibliography_node_id])) { $type_info = $this->getTypeInfo(self::FOOTNOTE_BIBLIOGRAPHY); // Transfer footnote content to the ordered list. self::$notes_ordered[$bibliography_node_id] = $bibliographies[$bibliography_node_id]; $note_num = array_key_exists($note_id, self::$written_notes) ? self::$written_notes[$note_id] : self::$footnote_counter++; $note_ref = $bibliography_node_id; } if (isset($type_info) && !empty($note_id) && !empty($note_num) && !empty($note_ref)) { $backlink_id = Kernel::get('DomId')->get($type_info['prefix'] . 'ref:' . $note_ref); $footlink_id = Kernel::get('DomId')->get($type_info['prefix'] . ':' . $note_ref); $attributes = array(); $attributes['rel'] = $type_info['name']; $attributes['href'] = '#' . $footlink_id; $attributes['counter'] = $note_num; $attributes['backlink_id'] = $backlink_id; return Kernel::get('OutputFormatBag')->buildTag($type_info['outputformat_methods']['link'], $note_num, $attributes); } return '[^' . $matches[1] . ']'; }
/** * Process paragraphs * * @param string $text The text to parse * * @return string The text parsed */ public function RebuildParagraph($text) { // Strip leading and trailing lines: $text = preg_replace('/\\A\\n+|\\n+\\z/', '', $text); $grafs = preg_split('/\\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); // Wrap <p> tags and unhashify HTML blocks foreach ($grafs as $key => $value) { $value = trim(Lexer::runGamut('span_gamut', $value)); // Check if this should be enclosed in a paragraph. // Clean tag hashes & block tag hashes are left alone. $is_p = !preg_match('/^B\\x1A[0-9]+B|^C\\x1A[0-9]+C$/', $value); if ($is_p) { $value = Kernel::get('OutputFormatBag')->buildTag('paragraph', $value); } $grafs[$key] = $value; } // Join grafs in one text, then unhash HTML tags. // $text = implode("\n\n", $grafs); $text = implode('', $grafs); // Finish by removing any tag hashes still present in $text. $text = Lexer::runGamut('filter:HTML:unhash', $text, true); return $text; }
/** * Set the page content if it is not set yet */ protected function _setContentTitle($string) { $old = Kernel::get(Kernel::TYPE_CONTENT)->getTitle(); if (empty($old)) { $meta = Kernel::get(Kernel::TYPE_CONTENT)->getMetadata(); $meta['title'] = $string; Kernel::get(Kernel::TYPE_CONTENT)->setMetadata($meta); } }
public function teardown($text) { $headers = array(); $content = Kernel::get(Kernel::TYPE_CONTENT); foreach ($content->getMetadata() as $name => $value) { if ($name === 'title') { $headers['name'] = $value; } elseif (in_array($name, self::$headers_meta_data)) { $headers[$name] = $value; } } $title = $content->getTitle(); if (empty($headers['name']) && $title) { $headers['name'] = $title; } $text = $this->buildTag('meta_title', null, $headers) . $text; return $text; }
/** * Gets a "safe string" from a `\DateTime` or an array * * @param mixed $source * @return string */ public static function getSafeString($source) { $str = $source; if (!is_string($source)) { if ($source instanceof \DateTime) { $str = Kernel::applyConfig('date_to_string', array($source)); } elseif (is_array($source)) { $str = ''; foreach ($source as $var => $val) { $str .= $var . ': ' . self::getSafeString($val) . PHP_EOL; } } } return $str; }
/** * Create a code span markup for $code. Called from handleSpanToken. * * @param string $code * @return string */ public function span($code) { $codeblock = Kernel::get('OutputFormatBag')->buildTag('code', Helper::escapeCodeContent(trim($code))); return parent::hashPart($codeblock); }
/** * Gets the metadata list as string * * @param array $metadata * @param \MarkdownExtended\API\ContentInterface $content * * @return string */ public function getMetadataToString(array $metadata, ContentInterface $content) { $specials = Kernel::getConfig('special_metadata'); $data = array(); foreach ($metadata as $var => $val) { if (!in_array($var, $specials)) { $data[] = $this->buildTag('meta_data', null, array('name' => $var, 'content' => $val)); } } return implode(PHP_EOL, $data); }
/** * Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. * * * Calls $hash_method to convert any blocks. * * Stops when the first opening tag closes. * * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. * (it is not inside clean tags) * * Returns an array of that form: ( processed text , remaining text ) * * @param string $text The text to be parsed * @param string $hash_method The method to execute * @param string $md_attr The attributes to add * @return array ( processed text , remaining text ) */ protected function _hashBlocks_inHTML($text, $hash_method, $md_attr) { if ($text === '') { return array('', ''); } // Regex to match `markdown` attribute inside of a tag. $markdown_attr_re = ' { \\s* # Eat whitespace before the `markdown` attribute markdown \\s*=\\s* (?> (["\']) # $1: quote delimiter (.*?) # $2: attribute value \\1 # matching delimiter | ([^\\s>]*) # $3: unquoted attribute value ) () # $4: make $3 always defined (avoid warnings) }xs'; // Regex to match any tag. $tag_re = '{ ( # $2: Capture hole tag. </? # Any opening or closing tag. [\\w:$]+ # Tag name. (?: (?=[\\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. (?> ".*?" | # Double quotes (can contain `>`) \'.*?\' | # Single quotes (can contain `>`) .+? # Anything but quotes and `>`. )*? )? > # End of tag. | <!-- .*? --> # HTML Comment | <\\?.*?\\?> | <%.*?%> # Processing instruction | <!\\[CDATA\\[.*?\\]\\]> # CData Block ) }xs'; $original_text = $text; // Save original text in case of faliure. $depth = 0; // Current depth inside the tag tree. $block_text = ""; // Temporary text holder for current text. $parsed = ""; // Parsed text that will be returned. // Get the name of the starting tag. // (This pattern makes $base_tag_name_re safe without quoting.) $base_tag_name_re = ''; if (preg_match('/^<([\\w:$]*)\\b/', $text, $matches)) { $base_tag_name_re = $matches[1]; } // Loop through every tag until we find the corresponding closing tag. do { // Split the text using the first $tag_match pattern found. // Text before pattern will be first in the array, text after // pattern will be at the end, and between will be any catches made // by the pattern. $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); if (count($parts) < 3) { // End of $text reached with unbalenced tag(s). // In that case, we return original text unchanged and pass the // first character as filtered to prevent an infinite loop in the // parent function. return array($original_text[0], substr($original_text, 1)); } $block_text .= $parts[0]; // Text before current tag. $tag = $parts[1]; // Tag to handle. $text = $parts[2]; // Remaining text after current tag. // Check for: Auto-close tag (like <hr/>) Comments and Processing Instructions. if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\\b}', $tag) || $tag[1] == '!' || $tag[1] == '?') { // Just add the tag to the block as if it was text. $block_text .= $tag; } else { // Increase/decrease nested tag count. Only do so if // the tag's name match base tag's. if (preg_match('{^</?' . $base_tag_name_re . '\\b}', $tag)) { if ($tag[1] == '/') { $depth--; } elseif ($tag[strlen($tag) - 2] != '/') { $depth++; } } // Check for `markdown="1"` attribute and handle it. if ($md_attr && preg_match($markdown_attr_re, $tag, $attr_m) && preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])) { // Remove `markdown` attribute from opening tag. $tag = preg_replace($markdown_attr_re, '', $tag); // Check if text inside this tag must be parsed in span mode. $mode = $attr_m[2] . $attr_m[3]; $span_mode = $mode == 'span' || $mode != 'block' && preg_match('{^<(?:' . $this->contain_span_tags_re . ')\\b}', $tag); // Calculate indent before tag. if (preg_match('/(?:^|\\n)( *?)(?! ).*?$/', $block_text, $matches)) { /* @var callable $strlen */ $strlen = Kernel::getConfig('utf8_strlen'); $indent = $strlen($matches[1], 'UTF-8'); } else { $indent = 0; } // End preceding block with this tag. $block_text .= $tag; $parsed .= $this->{$hash_method}($block_text); // Get enclosing tag name for the ParseMarkdown function. // (This pattern makes $tag_name_re safe without quoting.) preg_match('/^<([\\w:$]*)\\b/', $tag, $matches); $tag_name_re = $matches[1]; // Parse the content using the HTML-in-Markdown parser. list($block_text, $text) = self::_hashBlocks_inMarkdown($text, $indent, $tag_name_re, $span_mode); // Outdent markdown text. if ($indent > 0) { $block_text = preg_replace("/^[ ]{1,{$indent}}/m", "", $block_text); } // Append tag content to parsed text. if (!$span_mode) { $parsed .= "\n\n{$block_text}\n\n"; } else { $parsed .= "{$block_text}"; } // Start over a new block. $block_text = ""; } else { $block_text .= $tag; } } } while ($depth > 0); // Hash last block text that wasn't processed inside the loop. $parsed .= $this->{$hash_method}($block_text); return array($parsed, $text); }
/** * Add each link reference to `$urls` and `$titles` tables with index `$link_id` * * @param array $matches A set of results of the `transform()` function * @return string Empty string */ protected function _strip_callback($matches) { $link_id = strtolower($matches[1]); $url = $matches[2] == '' ? $matches[3] : $matches[2]; Kernel::addConfig('urls', array($link_id => $url)); Kernel::addConfig('titles', array($link_id => $matches[4])); Kernel::addConfig('attributes', array($link_id => $matches[5])); return ''; }
/** * {@inheritDoc} */ public function getMetadataFormatted() { return Kernel::get('OutputFormatBag')->getMetadataToString($this->metadata, $this); }
/** * Global gamut's method runner * * @param string $class * @param string $method * @param string $text * * @return string * * @throws \MarkdownExtended\Exception\UnexpectedValueException if `$gamut` doesn't implement the required method * or class can not be found */ protected function _runClassMethod($class, $method, $text) { if ($this->isCached($class)) { $_obj = $this->getCache($class); } else { if (!class_exists($class)) { throw new UnexpectedValueException(sprintf('Gamut class "%s" not found', $class)); } $_obj = new $class(); Kernel::validate($_obj, Kernel::TYPE_GAMUT, $class); $this->setCache($class, $_obj); } $method = $method ?: $_obj->getDefaultMethod(); if (!method_exists($_obj, $method)) { throw new UnexpectedValueException(sprintf('Method "%s" does not exist in class "%s"', $method, $class)); } $text = call_user_func(array($_obj, $method), $text); return $text; }
private function _clearHashes() { Kernel::setConfig('html_hashes', array()); Kernel::setConfig('cross_references', array()); Kernel::setConfig('urls', Kernel::getConfig('predefined_urls', array())); Kernel::setConfig('titles', Kernel::getConfig('predefined_titles', array())); Kernel::setConfig('attributes', Kernel::getConfig('predefined_attributes', array())); Kernel::setConfig('predefined_abbr', Kernel::getConfig('predefined_abbr', array())); }
/** * Process the fenced code blocks new lines * * @param array $matches * @return string */ protected function _newlines($matches) { return str_repeat(Kernel::get('OutputFormatBag')->buildTag('new_line'), strlen($matches[0])); }
/** * Build each maths span * * @param string $texblock * @return string */ public function span($texblock) { $texblock = trim($texblock); $block = Kernel::get('OutputFormatBag')->buildTag('maths_span', $texblock, array()); return parent::hashPart($block); }
/** * Build a table caption */ protected function _doCaption($matches) { return Kernel::get('OutputFormatBag')->buildTag('table_caption', $matches[0], array('id' => $this->table_id)); }