Make code highlighting work in Laravel.

28 July 2020 - 4 minute read

Since I wanted to show code snippets in a few of my articles, I considered highlighting the code snippets so that they would be easily readable. Most of the options out there are based on javascript libraries, which is fine, but I strive to minimize the amount of javascript as much as possible.

Frontend libraries

In my search for the best possible option, I first considered using a frontend javascript library. The two options I have looked at are:

In my first version of this website I used Vuejs for the frontend, in combination with a stripped down version of Prismjs. That means I only included the languages I thought were necessary for my articles. The bundle size was still small, but could be smaller.

Backend

While searching on Google I stumbled upon a PHP library called highlight.php made by scrivo. The library was a conversion of Highlightjs in PHP and applies the same css styling's from the original javascript library. It parses code strings, but can't parse them from a HTML string. So I had to write my own code to extract the code blocks, parse them and put them back in.

If you don't want to follow the steps, the complete code is at the bottom

1. Get the rendered markdown.

In my backend I am using Parsedown to render the markdown to a HTML string. I presume that using a different markdown parser makes no difference.

$parsedown = new Parsedown();
$rendered = $parsedown->text($markdown);

2. Find the code blocks

Since highlight.php can only highlight code strings and not search for code blocks in HTML, we need to find them ourselves. For this problem I am using PHP's DOMDocument where you can load a HTML string as a normal HTML tree.

Make sure to add the function, mb_convert_encoding(), this function will properly convert special character to the UTF-8 format. I learned this the hard way because i tried to convert french and it didn't convert properly.

$dom = new \DOMDocument();
$dom->loadHtml(mb_convert_encoding($rendered, 'HTML-ENTITIES', 'UTF-8'));

Now that we have loaded the DOMDocument with our HTML it is time to search for the code blocks.

$code_elements = $dom->getElementsByTagName("code");

3. Highlighting the code blocks.

Now that we have all the code blocks from the rendered markdown, we can get the text from the element and highlight it using the library.

But, first we need to know which language we are dealing with. To get this information we need to get the class name of the code block as this is where the language is stored. Since the library uses the original styles from the javascript library we need to replace the class name to hljs.

$language = str_replace('language-', '', $element->getAttribute('class'));
$element->setAttribute('class', 'hljs');

Next we need to get the actual code that is stored inside the element and highlight it using the library. In the previous code snippet we stored the language, in the highlight function we can use this again to make sure the library highlights it in the right language.

$code = $element->firstChild->wholeText;
$highlighted = $hl->highlight($language, $code);

4. Replacing the code blocks

After highlighting the code blocks it is time to put back the highlighted HTML. We first need to empty the element. Make a separate DOMDocumentFragment which is basically a node without a parent. Append the highlighted code blocks and append it back in to the original element.

element->firstChild->nodeValue = '';
$template = $dom->createDocumentFragment();
$template->appendXML($highlighted->value);
$element->appendChild($template);

5. Save the HTML

We have reconstructed our rendered markdown! Now we need to save it and store it.

$rendered = $dom->saveHTML();
$this->attributes['rendered'] = $rendered;

6. Don't forget the highlightjs stylings!

You are probably pumped to see the result of the hard work you did and come to the conclusion nothing has changed. Make sure to pick a theme from the highlightjs demo website and include the css in your custom css file.

Complete code

For lazy ones that don't want to follow steps and want to mess around with the code, here is the complete function.

$parsedown = new Parsedown();

$rendered = $parsedown->text($markdown);

//initialize the highlighter once
$hl = new \Highlight\Highlighter();

//load html
$dom = new \DOMDocument();
$dom->loadHtml(mb_convert_encoding($rendered, 'HTML-ENTITIES', 'UTF-8'));

//search for the code blocks
$code_elements = $dom->getElementsByTagName("code");

//highlight each found code block
if(count($code_elements) > 0){
    foreach ($code_elements as $element) {
        $language = str_replace('language-', '', $element->getAttribute('class'));
        $element->setAttribute('class', 'hljs');

        $code = $element->firstChild->wholeText;
        $highlighted = $hl->highlight($language, $code);

        $element->firstChild->nodeValue = '';
        $template = $dom->createDocumentFragment();
        $template->appendXML($highlighted->value);
        $element->appendChild($template);
    }
}

//save the new rendered article with code highlighting
$rendered = $dom->saveHTML();

//save it in the database
$this->attributes['rendered'] = $rendered;