Example #1
0
 /**
  * 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);
 }
Example #2
0
 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);
 }
Example #3
0
 /**
  * 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);
 }
Example #4
0
 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('&#35;', '#', $item['content']);
     $event->setItem($item);
 }
Example #5
0
 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);
 }
Example #6
0
 /**
  * @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']);
 }
Example #7
0
 /**
  * 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);
 }
Example #8
0
 /**
  * @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']);
             }
         }
     }
 }
Example #9
0
 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);
 }
Example #10
0
    /**
     * 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);
    }
Example #11
0
 public function onItemPostParse(ParseEvent $event)
 {
     $html = str_replace('<em>eAsYbOoK</em>', '<strong class="branding">easybook</strong>', $event->getItemProperty('content'));
     $event->setItemProperty('content', $html);
 }
Example #12
0
 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);
     }
 }
Example #13
0
 /**
  * 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);
 }