/** * It decorates each table with a template and, if the edition configures it, * with the appropriate auto-numbered label. * * @param ParseEvent $event The object that contains the item being processed */ public function decorateAndLabelTables(ParseEvent $event) { $item = $event->getItem(); $addTableLabels = in_array('table', $event->app->edition('labels') ?: array()); $parentItemNumber = $item['config']['number']; $listOfTables = array(); $counter = 0; $item['content'] = preg_replace_callback("/(?<content><table.*\n<\\/table>)/Ums", function ($matches) use($event, $addTableLabels, $parentItemNumber, &$listOfTables, &$counter) { // prepare table parameters for template and label $counter++; $parameters = array('item' => array('caption' => '', 'content' => $matches['content'], 'label' => '', 'number' => $counter, 'slug' => $event->app->slugify('Table ' . $parentItemNumber . '-' . $counter)), 'element' => array('number' => $parentItemNumber)); // the publishing edition wants to label tables if ($addTableLabels) { $label = $event->app->getLabel('table', $parameters); $parameters['item']['label'] = $label; } // add table details to the list-of-tables $listOfTables[] = $parameters; return $event->app->render('table.twig', $parameters); }, $item['content']); if (count($listOfTables) > 0) { $event->app->append('publishing.list.tables', $listOfTables); } $event->setItem($item); }
public function onItemPostParse(ParseEvent $event) { // fix image URLs $item = $event->getItem(); $item['content'] = preg_replace_callback('/<img src="(.*)"(.*) \\/>/U', function ($matches) { $uri = $matches[1]; if ('images/' != substr($uri, 0, 7)) { $uri = 'images/' . $uri; } return sprintf('<img src="%s"%s />', $uri, $matches[2]); }, $item['content']); $event->setItem($item); // decorate each image with a template (and add labels if needed) $item = $event->getItem(); $listOfImages = array(); $elementNumber = $item['config']['number']; $counter = 0; $item['content'] = preg_replace_callback('/(?<content><img .*alt="(?<title>.*)".*\\/>)/U', function ($matches) use($event, $elementNumber, &$listOfImages, &$counter) { // prepare item parameters for template and label $counter++; $parameters = array('item' => array('caption' => $matches['title'], 'content' => $matches['content'], 'label' => '', 'number' => $counter, 'slug' => $event->app->get('slugger')->slugify('Figure ' . $elementNumber . '-' . $counter)), 'element' => array('number' => $elementNumber)); // the publishing edition wants to label figures/images if (in_array('figure', $event->app->edition('labels'))) { $label = $event->app->getLabel('figure', $parameters); $parameters['item']['label'] = $label; } // add image datails to list-of-images $listOfImages[] = $parameters; return $event->app->render('figure.twig', $parameters); }, $item['content']); $event->app->append('publishing.list.images', $listOfImages); $event->setItem($item); }
/** * It decorates each image with a template and, if the edition configures it, * with the appropriate auto-numbered label. * * @param ParseEvent $event The object that contains the item being processed */ public function decorateAndLabelImages(ParseEvent $event) { $item = $event->getItem(); $addImageLabels = in_array('figure', $event->app->edition('labels') ?: array()); $parentItemNumber = $item['config']['number']; $listOfImages = array(); $counter = 0; $item['content'] = preg_replace_callback('/(<p>)?(<div class="(?<align>.*)">)?(?<content><img .*alt="(?<title>[^"]*)".*\\/>)(<\\/div>)?(<\\/p>)?/', function ($matches) use($event, $addImageLabels, $parentItemNumber, &$listOfImages, &$counter) { // prepare figure parameters for the template and the label $parameters = array('item' => array('align' => $matches['align'], 'caption' => $matches['title'], 'content' => $matches['content'], 'label' => '', 'number' => null, 'slug' => ''), 'element' => array('number' => $parentItemNumber)); // '*' in title means this is a decorative image instead of // a book figure or illustration if ('*' != $matches['title']) { $counter++; $parameters['item']['number'] = $counter; $parameters['item']['slug'] = $event->app->slugify('Figure ' . $parentItemNumber . '-' . $counter); // the publishing edition wants to label figures/images if ($addImageLabels) { $label = $event->app->getLabel('figure', $parameters); $parameters['item']['label'] = $label; } // add image details to the list-of-images $listOfImages[] = $parameters; } return $event->app->render('figure.twig', $parameters); }, $item['content']); if (count($listOfImages) > 0) { $event->app->append('publishing.list.images', $listOfImages); } $event->setItem($item); }
public function onItemPostParse(ParseEvent $event) { $item = $event->getItem(); // unescape yaml-style comments that before parsing could // be interpreted as Markdown first-level headings $item['content'] = str_replace('#', '#', $item['content']); $event->setItem($item); }
public function onItemPostParse(ParseEvent $event) { $format = $event->app->edition('format'); if (!in_array($format, array('html_chunked', 'epub', 'epub2'))) { return; } $item = $event->getItem(); $links = $event->app->get('publishing.links'); foreach ($item['toc'] as $entry) { $itemSlug = $event->app->get('slugger')->slugify(trim($item['label']), array('unique' => false)); $relativeUrl = '#' . $entry['slug']; $absoluteUrl = $itemSlug . '.html' . $relativeUrl; $links[$relativeUrl] = $absoluteUrl; } $event->app->set('publishing.links', $links); }
/** * @dataProvider getCodeBlockConfiguration * * @param string $inputFilePath The contents to be parsed * @param string $expectedFilePath The expected result of parsing the contents * @param string $codeBlockType The type of code block used in the content * @param boolean $enableCodeHightlight Whether or not code listings should be highlighted */ public function testCodeBlocksTypes($inputFilePath, $expectedFilePath, $codeBlockType, $enableCodeHightlight) { $fixturesDir = __DIR__ . '/fixtures/code/'; $app = $this->getApp($codeBlockType, $enableCodeHightlight); $plugin = new CodePlugin(); $event = new ParseEvent($app); $event->setItem(array('config' => array('format' => 'md'), 'original' => file_get_contents($fixturesDir . '/' . $inputFilePath), 'content' => '')); // execute pre-parse method of the plugin $plugin->parseCodeBlocks($event); $item = $event->getItem(); // parse the item original content $item['content'] = $app['parser']->transform($item['original']); // execute post-parse method of the plugin $event->setItem($item); $plugin->fixParsedCodeBlocks($event); $item = $event->getItem(); $this->assertEquals(file_get_contents($fixturesDir . '/' . $expectedFilePath), $item['content']); }
/** * It adds the appropriate auto-numbered labels to the book item sections. * * @param ParseEvent $event The object that contains the item being processed */ public function addSectionLabels(ParseEvent $event) { $item = $event->getItem(); // special book items without a TOC don't need labels if (0 == count($item['toc'])) { return; } $counters = array(1 => $item['config']['number'], 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0); $addSectionLabels = in_array($item['config']['element'], $event->app->edition('labels') ?: array()); foreach ($item['toc'] as $key => $entry) { if ($addSectionLabels) { $level = $entry['level']; if ($level > 1) { $counters[$level]++; } // reset the counters for the higher heading levels for ($i = $level + 1; $i <= 6; $i++) { $counters[$i] = 0; } $parameters = array_merge($item['config'], array('counters' => $counters, 'level' => $level)); $label = $event->app->getLabel($item['config']['element'], array('item' => $parameters)); } else { $label = ''; } $entry['label'] = $label; $item['toc'][$key] = $entry; } // the label of the item matches the label of its first TOC element $item['label'] = $item['toc'][0]['label']; // add section labels to the content foreach ($item['toc'] as $i => $entry) { // the parsed title can be different from the TOC entry title // that's the case for the titles with markup code inside (* ` ** etc.) // thus, the replacement must be done based on a fuzzy title that // doesn't include the title text $fuzzyTitle = "/<h" . $entry['level'] . " id=\"" . $entry['slug'] . "\">.*<\\/h" . $entry['level'] . ">\n\n/"; $labeledTitle = sprintf("<h%s id=\"%s\">%s%s</h%s>\n\n", $entry['level'], $entry['slug'], $entry['label'], '' != $entry['label'] ? ' ' . $entry['title'] : $entry['title'], $entry['level']); $item['content'] = preg_replace($fuzzyTitle, $labeledTitle, $item['content']); } $event->setItem($item); }
/** * @dataProvider getTestTablePluginData */ public function testTablePlugin($inputFilePath, $expectedFilePath, $itemNumber, $addLabels, $expectedLabels) { $fixturesDir = __DIR__ . '/fixtures/tables/'; $app = new Application(); $app['publishing.book.slug'] = 'test_book'; $app['publishing.edition'] = 'test_edition'; $app['publishing.book.config'] = array('book' => array('slug' => 'test_book', 'language' => 'en', 'editions' => array('test_edition' => array('format' => 'html', 'labels' => $addLabels ? array('table') : array(), 'theme' => 'clean')))); $event = new ParseEvent($app); $plugin = new TablePlugin(); $event->setItem(array('config' => array('number' => $itemNumber), 'content' => file_get_contents($fixturesDir . '/' . $inputFilePath))); $plugin->decorateAndLabelTables($event); $item = $event->getItem(); $this->assertEquals(file_get_contents($fixturesDir . '/' . $expectedFilePath), $item['content']); if (count($app['publishing.list.tables']) > 0) { foreach ($app['publishing.list.tables'] as $i => $table) { $this->assertRegexp('/<table.*<\\/table>/s', $table[$i]['item']['content']); if ($addLabels) { $this->assertEquals($expectedLabels[$i], $table[$i]['item']['label']); } } } }
public function onItemPostParse(ParseEvent $event) { // decorate each table with a template (and add labels if needed) $item = $event->getItem(); $listOfTables = array(); $elementNumber = $item['config']['number']; $counter = 0; $item['content'] = preg_replace_callback('/(?<content><table.*\\n<\\/table>)/Ums', function ($matches) use($event, $elementNumber, &$listOfTables, &$counter) { // prepare item parameters for template and label $counter++; $parameters = array('item' => array('caption' => '', 'content' => $matches['content'], 'label' => '', 'number' => $counter, 'slug' => $event->app->get('slugger')->slugify('Table ' . $elementNumber . '-' . $counter)), 'element' => array('number' => $elementNumber)); // the publishing edition wants to label tables if (in_array('table', $event->app->edition('labels'))) { $label = $event->app->getLabel('table', $parameters); $parameters['item']['label'] = $label; } // add table datails to list-of-images $listOfTables[] = $parameters; return $event->app->render('table.twig', $parameters); }, $item['content']); $event->app->append('publishing.list.tables', $listOfTables); $event->setItem($item); }
/** * It parses the code blocks of the item content that use the * fenced style for code blocks: * * the code listing starts with at least three ~~~ * * (optionally) followed by a whitespace + a dot + the programming language name * * the lines of code don't include any leading tab or whitespace * * the code listing ends with the same number of opening ~~~ * * Examples: * * ~~~ .php * $lorem = 'ipsum'; * // ... * ~~~ * * ~~~~~~~~~~ .javascript * var lorem = 'ipsum'; * // ... * ~~~~~~~~~~ * * ~~~ * Generic code not associated with any language * ~~~ * * @param ParseEvent $event The event object that provides access to the $app and * the $item being parsed */ private function parseFencedTypeCodeBlocks(ParseEvent $event) { // variable needed for PHP 5.3 $self = $this; $item = $event->getItem(); // regexp adapted from PHP-Markdown $item['original'] = preg_replace_callback('{ (?:\\n|\\A) # 1: Opening marker ( ~{3,} # Marker: three tilde or more. ) [ ]* (?: \\.?([-_:a-zA-Z0-9]+) # 2: standalone class name )? [ ]* \\n # Whitespace and newline following marker. # 4: Content ( (?> (?!\\1 [ ]* \\n) # Not a closing marker. .*\\n+ )+ ) # Closing marker. \\1 [ ]* \\n }Uxm', function ($matches) use($self, $event) { $language = $matches[2]; // codeblocks always end with an empty new line (due to the regexp used) // the current solution rtrims() the whole block. This would not work // in the (very) rare situations where a code block must end with // whitespaces, tabs or new lines $code = rtrim($matches[3]); if ('' == $language) { $language = 'code'; } $code = $self->highlightAndDecorateCode($code, $language, $event->app); return "\n\n" . $code; }, $item['original']); $event->setItem($item); }
public function onItemPostParse(ParseEvent $event) { $html = str_replace('<em>eAsYbOoK</em>', '<strong class="branding">easybook</strong>', $event->getItemProperty('content')); $event->setItemProperty('content', $html); }
public function onItemPostParse(ParseEvent $event) { // replace <br> by <br/> (it causes problems for epub books) $item = $event->getItem(); $item['content'] = str_replace('<br>', '<br/>', $item['content']); $event->setItem($item); // strip title from the parsed content $item = $event->getItem(); if (count($item['toc']) > 0) { $heading = $item['toc'][0]; // only <h1> headings can be the title of the content if (1 == $heading['level']) { // the <h1> heading must be the first line to consider it a title $item['content'] = preg_replace('{ ^<h1.*<\\/h1>\\n+(.*) }x', '$1', $item['content']); $item['slug'] = $heading['slug']; $item['title'] = $heading['title']; } } $event->setItem($item); // add labels $item = $event->getItem(); if (count($item['toc']) > 0) { // prepare labels $counters = array(1 => $item['config']['number'], 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0); foreach ($item['toc'] as $key => $entry) { // edition config allows labels for this element type ('labels' option) if (in_array($item['config']['element'], $event->app->edition('labels'))) { $level = $entry['level']; if ($level > 1) { $counters[$level]++; } // Reset the counters for the higher heading levels for ($i = $level + 1; $i <= 6; $i++) { $counters[$i] = 0; } $parameters = array_merge($item['config'], array('counters' => $counters, 'level' => $level)); $label = $event->app->getLabel($item['config']['element'], array('item' => $parameters)); } else { $label = ""; } $entry['label'] = $label; $item['toc'][$key] = $entry; } // the label of the item matches the label of the first toc element $item['label'] = $item['toc'][0]['label']; $event->setItem($item); // add labels to content $item = $event->getItem(); foreach ($item['toc'] as $i => $entry) { // the parsed title can be different from the toc entry title // that's the case for the titles with markup code inside (* ` ** etc.) // thus, the replacement must be done based on a fuzzy title that // doesn't include the title text $fuzzyTitle = "/<h" . $entry['level'] . " id=\"" . $entry['slug'] . "\">.*<\\/h" . $entry['level'] . ">\n\n/"; $labeledTitle = sprintf("<h%s id=\"%s\">%s %s</h%s>\n\n", $entry['level'], $entry['slug'], $entry['label'], $entry['title'], $entry['level']); $item['content'] = preg_replace($fuzzyTitle, $labeledTitle, $item['content']); } $event->setItem($item); } // ensure that the item has a title (using the default title if necessary) $item = $event->getItem(); if ('' == $item['title']) { $item['title'] = $event->app->getTitle($item['config']['element']); $item['slug'] = $event->app->get('slugger')->slugify($item['title']); $event->setItem($item); } }
/** * It marks the internal links of the book used for cross-references. This * allows to display the internal links differently than the regular links. * * @param ParseEvent $event The object that contains the item being processed */ public function markInternalLinks(ParseEvent $event) { // Internal links are only marked for the PDF editions if ('pdf' != $event->app->edition('format')) { return; } $item = $event->getItem(); $item['content'] = preg_replace_callback('/<a (href="#.*".*)<\\/a>/Us', function ($matches) { return sprintf('<a class="internal" %s</a>', $matches[1]); }, $item['content']); $event->setItem($item); }