📚 Table of Contents

  1. Introduction to Parsedown
  2. Standard Markdown Syntax Supported
  3. Special Features
  4. Handling Images
  5. Handling Links
  6. Using Parsedown in PHP
  7. Best Practices
  8. Additional Resources

Introduction to Parsedown

Parsedown is a Markdown parser written in PHP. It's designed to be fast, easy to use, and highly compatible with the Markdown.pl reference implementation. Parsedown converts Markdown text into HTML, making it ideal for rendering user-generated content, blog posts, documentation, and more.

Key Features:

  • Speed: Optimized for performance.
  • Extensibility: Easily extendable for custom needs.
  • GitHub Flavored Markdown (GFM): Supports many GFM features.
  • Security: Automatically escapes HTML to prevent XSS attacks (with options to allow certain HTML).

Standard Markdown Syntax Supported

Parsedown supports a wide range of standard Markdown syntax. Here's a rundown of the most common elements.

Headers

Use # symbols to create headers. The number of # symbols indicates the header level.

# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6

Rendered HTML:

<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

Emphasis

Italics

Wrap text in single asterisks (*) or underscores (_).

*italic* or _italic_

Bold

Wrap text in double asterisks (**) or double underscores (__).

**bold** or __bold__

Strikethrough

Wrap text in double tildes (~~).

~~strikethrough~~

Rendered HTML:

<p><em>italic</em> or <em>italic</em></p>
<p><strong>bold</strong> or <strong>bold</strong></p>
<p><del>strikethrough</del></p>

Lists

Unordered Lists

Use asterisks (*), pluses (+), or hyphens (-).

- Item 1
- Item 2
  - Subitem 2.1
  - Subitem 2.2
* Item 3

Ordered Lists

Use numbers followed by periods.

1. First item
2. Second item
   1. Subitem 2.1
   2. Subitem 2.2
3. Third item

Rendered HTML:

<ul>
  <li>Item 1</li>
  <li>Item 2
    <ul>
      <li>Subitem 2.1</li>
      <li>Subitem 2.2</li>
    </ul>
  </li>
  <li>Item 3</li>
</ul>
<ol>
  <li>First item</li>
  <li>Second item
    <ol>
      <li>Subitem 2.1</li>
      <li>Subitem 2.2</li>
    </ol>
  </li>
  <li>Third item</li>
</ol>

Create hyperlinks using square brackets for text and parentheses for the URL.

[OpenAI](https://openai.com)

Rendered HTML:

<p><a href="https://openai.com">OpenAI</a></p>

Images

Embed images using an exclamation mark (!), square brackets for alt text, and parentheses for the image URL.

![Alt Text](https://example.com/image.jpg)

Rendered HTML:

<p><img src="https://example.com/image.jpg" alt="Alt Text" /></p>

Code Blocks

Inline Code

Wrap code snippets in backticks (`).

Use the `echo` statement in PHP.

Rendered HTML:

<p>Use the <code>echo</code> statement in PHP.</p>

Fenced Code Blocks

Use triple backticks (```) or tildes (~~~) to create code blocks. Optionally specify the language for syntax highlighting.

<?php
echo "Hello, World!";
?>
console.log("Hello, World!");

Rendered HTML:

<pre><code class="language-php">&lt;?php
echo "Hello, World!";
?&gt;
</code></pre>
<pre><code class="language-javascript">console.log("Hello, World!");
</code></pre>

Blockquotes

Use the greater-than symbol (>).

> This is a blockquote.
> 
> It can span multiple paragraphs.

Rendered HTML:

<blockquote>
  <p>This is a blockquote.</p>
  <p>It can span multiple paragraphs.</p>
</blockquote>

Horizontal Rules

Create horizontal lines using three or more hyphens (---), asterisks (***), or underscores (___).

---

Rendered HTML:

<hr />

Tables

Use pipes (|) and hyphens (-) to create tables. Parsedown supports tables similar to GitHub Flavored Markdown.

| Header 1 | Header 2 |
|----------|----------|
| Row 1    | Data     |
| Row 2    | More Data|

Rendered HTML:

<table>
  <thead>
    <tr>
      <th>Header 1</th>
      <th>Header 2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Row 1</td>
      <td>Data</td>
    </tr>
    <tr>
      <td>Row 2</td>
      <td>More Data</td>
    </tr>
  </tbody>
</table>

Inline HTML

You can include HTML directly within your Markdown. Parsedown allows certain HTML elements but sanitizes to prevent XSS attacks.

<p>This is an HTML paragraph.</p>

Rendered HTML:

<p>This is an HTML paragraph.</p>

Special Features

Beyond standard Markdown, Parsedown offers additional features that enhance its functionality.

GitHub Flavored Markdown (GFM) Extensions

Parsedown includes support for many GFM features, enhancing the standard Markdown syntax.

Task Lists

Create checklists using a combination of hyphens, brackets, and whitespace.

- [x] Task 1
- [ ] Task 2
- [x] Task 3

Rendered HTML:

<ul>
  <li><input type="checkbox" checked disabled> Task 1</li>
  <li><input type="checkbox" disabled> Task 2</li>
  <li><input type="checkbox" checked disabled> Task 3</li>
</ul>

Note: The default Parsedown may not support task lists out of the box. You might need to extend it or use a Parsedown extension like Parsedown Extra.

Tables Alignment

Specify alignment for table columns.

| Left Align | Center Align | Right Align |
|:-----------|:------------:|------------:|
| Data       | Data         | Data        |
| More Data  | More Data    | More Data   |

Rendered HTML:

<table>
  <thead>
    <tr>
      <th style="text-align:left;">Left Align</th>
      <th style="text-align:center;">Center Align</th>
      <th style="text-align:right;">Right Align</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Data</td>
      <td>Data</td>
      <td>Data</td>
    </tr>
    <tr>
      <td>More Data</td>
      <td>More Data</td>
      <td>More Data</td>
    </tr>
  </tbody>
</table>

Syntax Highlighting

Parsedown can integrate with syntax highlighting libraries (e.g., Prism.js, Highlight.js) to style code blocks based on the specified language.

Example with Highlight.js:

  1. Include Highlight.js in Your HTML:

    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
    <script>hljs.highlightAll();</script>
  2. Markdown with Language-Specified Code Blocks:

    ```python
    def hello_world():
       print("Hello, world!")

Rendered HTML:

<pre><code class="language-python">def hello_world():
    print("Hello, world!")
</code></pre>

Note: To enable syntax highlighting, ensure that the appropriate CSS and JS libraries are included in your project as shown above.

Custom Extensions

Parsedown can be extended to support custom Markdown syntax or to modify existing behavior. This is useful for adding specific features like custom HTML tags, macros, or new block types.

Creating a Custom Parsedown Extension:

  1. Create a Custom Parser Class:

    <?php
    use Parsedown;
    
    class ParsedownExtended extends Parsedown {
       // Override methods or add new ones here.
    
       // Example: Add a custom block type for "spoiler" sections.
       protected function blockSpoiler($Line) {
           if (preg_match('/^\>\>\>(.*)/', $Line['text'], $matches)) {
               return [
                   'type' => 'spoiler',
                   'element' => [
                       'name' => 'div',
                       'attributes' => [
                           'class' => 'spoiler'
                       ],
                       'text' => trim($matches[1]),
                       'handler' => 'line'
                   ]
               ];
           }
       }
    
       protected function blockSpoilerContinue($Line, $Block) {
           if (strpos($Line['text'], '>>>') === 0) {
               $Block['element']['text'] .= "\n" . trim(substr($Line['text'], 3));
               return $Block;
           }
       }
    
       protected function inlineSpoiler($Excerpt) {
           if (preg_match('/\>\>([^<>]+)\<\<$/', $Excerpt['text'], $matches)) {
               return [
                   'extent' => strlen($matches[0]),
                   'element' => [
                       'name' => 'span',
                       'attributes' => [
                           'class' => 'spoiler-text'
                       ],
                       'text' => $matches[1]
                   ]
               ];
           }
       }
    }
  2. Use the Custom Parser in Your Controller:

    <?php
    namespace App\Controllers;
    use CodeIgniter\Controller;
    use App\Models\ContentModel;
    require_once APPPATH . 'ThirdParty/ParsedownExtended.php';
    
    class Pages extends Controller
    {
       protected $contentModel;
       protected $parsedown;
    
       public function __construct()
       {
           helper(['url', 'form']);
           $this->contentModel = new ContentModel();
           $this->parsedown = new \ParsedownExtended();
       }
    
       public function view($page = 'home')
       {
           // Sanitize and process page parameter
           $page = filter_var($page, FILTER_SANITIZE_STRING);
           $page = str_replace('/', DIRECTORY_SEPARATOR, $page);
    
           $data['title'] = ucfirst(str_replace(['-', DIRECTORY_SEPARATOR], ' ', $page));
           $markdownContent = $this->contentModel->getPageContent($page);
    
           if (!$markdownContent) {
               throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
           }
    
           // Use ParsedownExtended to parse Markdown
           $data['content'] = $this->parsedown->text($markdownContent);
    
           // Load views
           echo view('templates/header', $data);
           echo view('pages/view', $data);
           echo view('templates/footer', $data);
       }
    }
  3. Define CSS for Custom Elements:

    .spoiler {
       background-color: #333;
       color: #333;
       position: relative;
    }
    
    .spoiler:hover {
       color: #fff;
    }
    
    .spoiler-text {
       background-color: #333;
       color: #333;
       position: relative;
    }
    
    .spoiler-text:hover {
       color: #fff;
    }

This example adds a custom "spoiler" block and inline syntax, enhancing your Markdown's capabilities.


Handling Images

Images are a fundamental part of Markdown content. Here's how to effectively manage them using Parsedown.

Syntax

![Alt Text](https://example.com/path/to/image.jpg "Optional Title")
  • Alt Text: Provides alternative text for screen readers and displays if the image cannot load.
  • URL: Path to the image. Can be external (absolute URL) or internal (relative path).
  • Title (Optional): Additional information shown as a tooltip on hover.

Example:

![OpenAI Logo](https://openai.com/logo.png "OpenAI")

Rendered HTML:

<p><img src="https://openai.com/logo.png" alt="OpenAI Logo" title="OpenAI" /></p>

External vs. Internal Images

  • External Images: Host your images on external servers or CDNs.

    ![External Image](https://cdn.example.com/images/picture.jpg)
  • Internal Images: Store images within your project directory.

    ![Local Image](/assets/images/picture.jpg)

Best Practices for Internal Images:

  1. Organize Images: Keep all images in a designated directory, such as /public/assets/images/.
  2. Use Relative Paths: Ensure the paths correctly reflect the image locations relative to the served HTML.

Setting Image Attributes

While standard Markdown allows you to set alt text and title, you might want to add additional attributes like class, id, or inline styles.

Example with HTML Attributes:

![Alt Text](https://example.com/image.jpg "Title"){.class #id style="width:100px;"}

Note: Parsedown does not support extension syntax out of the box for adding classes or IDs to images. To enable this functionality, you need to extend Parsedown or use Parsedown Extra.

Using Parsedown Extra:

If you require more advanced features like adding classes or IDs, consider using Parsedown Extra:

  1. Install Parsedown Extra via Composer:

    composer require erusev/parsedown-extra
  2. Use Parsedown Extra in Your Controller:

    <?php
    use ParsedownExtra;
    
    $parsedown = new ParsedownExtra();
    $markdown = '![Alt Text](https://example.com/image.jpg "Title"){.class #id}';
    $html = $parsedown->text($markdown);
    
    echo $html;

Rendered HTML with Parsedown Extra:

<p><img src="https://example.com/image.jpg" alt="Alt Text" title="Title" class="class" id="id" /></p>

Links enrich your content by connecting it to other resources. Here's how to manage them effectively with Parsedown.

Internal vs. External URLs

  • Internal Links: Point to pages within your website.

    [Home](/home)
  • External Links: Point to external websites.

    [OpenAI](https://openai.com)

Best Practices:

  • Consistency: Use absolute paths (/home) for internal links to avoid issues with relative paths.
  • Clarity: Clearly distinguish between internal and external links for better user experience.

To have links open in new browser tabs, you need to add the target="_blank" attribute. Standard Markdown doesn't support this directly, so you need to extend Parsedown or use custom syntax.

Using Inline HTML:

<a href="https://openai.com" target="_blank">OpenAI</a>

Automatically Adding target="_blank" to External Links:

You can extend Parsedown to modify the link rendering behavior.

Example:

<?php
use Parsedown;

class ParsedownWithTarget extends Parsedown {
    protected function inlineLink($Excerpt) {
        $link = parent::inlineLink($Excerpt);
        if ($link) {
            $link['element']['attributes']['target'] = '_blank';
        }
        return $link;
    }
}

// Usage
$parsedown = new ParsedownWithTarget();
$markdown = '[OpenAI](https://openai.com)';
$html = $parsedown->text($markdown);

echo $html;

Rendered HTML:

<p><a href="https://openai.com" target="_blank">OpenAI</a></p>

You can add titles to links, which appear as tooltips when users hover over them.

[OpenAI](https://openai.com "Visit OpenAI's website")

Rendered HTML:

<p><a href="https://openai.com" title="Visit OpenAI's website">OpenAI</a></p>

Using Parsedown in PHP

Installation

If you haven't already installed Parsedown, you can add it to your project using Composer:

composer require erusev/parsedown

Basic Usage

Here's a simple example of using Parsedown to convert Markdown to HTML.

<?php
require 'vendor/autoload.php';

$parsedown = new Parsedown();

$markdown = "# Hello World\nThis is a **bold** statement.";
$html = $parsedown->text($markdown);

echo $html;

Output:

<h1>Hello World</h1>
<p>This is a <strong>bold</strong> statement.</p>

Advanced Configuration

Parsedown allows some customization through its API. You can override methods to change parsing behavior or extend the class to add new features.

Customizing Markdown Parsing:

<?php
use ParsedownExtended;

class MyParsedown extends ParsedownExtended {
    // Override methods or add your own
}

// Usage
$parsedown = new MyParsedown();
$markdown = "Your **Markdown** text here.";
$html = $parsedown->text($markdown);
echo $html;

Best Practices

Security Considerations

When rendering user-generated Markdown content, it's crucial to prioritize security to prevent Cross-Site Scripting (XSS) attacks.

Tips:

  1. Escape HTML: Parsedown escapes HTML by default, but always validate and sanitize user inputs.
  2. Limit Allowed HTML Tags: If you allow inline HTML, restrict it to a safe subset.
  3. Use Libraries Like HTML Purifier: For additional security, consider passing the rendered HTML through a sanitization library.

Example Using HTML Purifier:

  1. Install HTML Purifier via Composer:

    composer require ezyang/htmlpurifier
  2. Sanitize Parsed HTML:

    <?php
    require 'vendor/autoload.php';
    
    $config = HTMLPurifier_Config::createDefault();
    $purifier = new HTMLPurifier($config);
    
    $parsedown = new Parsedown();
    $markdown = 'Your **Markdown** text with <script>alert("XSS")</script> here.';
    $dirty_html = $parsedown->text($markdown);
    $clean_html = $purifier->purify($dirty_html);
    
    echo $clean_html;

Output:

<p>Your <strong>Markdown</strong> text with  here.</p>

The <script> tag is removed, preventing XSS attacks.

Customizing Output

Depending on your project's needs, you might want to customize how Parsedown renders certain elements.

Examples:

  • Adding Custom Classes: Add specific CSS classes to elements for styling.
  • Modifying Elements: Change the structure or behavior of generated HTML elements.

Example: Adding a Custom Class to All <p> Tags:

<?php
use Parsedown;

class ParsedownCustom extends Parsedown {
    protected function element(array $Element) {
        if ($Element['name'] === 'p') {
            $Element['attributes']['class'] = 'custom-paragraph';
        }
        return parent::element($Element);
    }
}

// Usage
$parsedown = new ParsedownCustom();
$markdown = 'This is a paragraph.';
$html = $parsedown->text($markdown);

echo $html;

Rendered HTML:

<p class="custom-paragraph">This is a paragraph.</p>

Caching Parsed Markdown

If your Markdown content doesn't change frequently, consider caching the parsed HTML to improve performance.

Implementation Steps:

  1. Parse Markdown and Cache HTML:

    <?php
    use Parsedown;
    use CodeIgniter\Cache\CacheInterface;
    
    class ContentModel extends Model
    {
       protected $contentPath = WRITEPATH . 'content/';
       protected $parsedown;
       protected $cache;
    
       public function __construct()
       {
           parent::__construct();
           $this->parsedown = new Parsedown();
           $this->cache = \Config\Services::cache();
       }
    
       public function getPageContent($page)
       {
           // Sanitize page parameter
           $page = str_replace(['../', './'], '', $page);
           $filePath = $this->contentPath . $page . '.md';
           $cacheKey = 'page_content_' . $page;
    
           // Check cache
           if ($this->cache->get($cacheKey)) {
               return $this->cache->get($cacheKey);
           }
    
           if (file_exists($filePath)) {
               $markdown = file_get_contents($filePath);
               $html = $this->parsedown->text($markdown);
               // Save to cache for 10 minutes
               $this->cache->save($cacheKey, $html, 600);
               return $html;
           }
           return false;
       }
    }
  2. Clear Cache on Content Update:

    Ensure that whenever a Markdown file is updated, the corresponding cache is cleared to reflect changes.

    public function updatePageContent($page, $newContent)
    {
       $filePath = $this->contentPath . $page . '.md';
       if (file_put_contents($filePath, $newContent) !== false) {
           // Clear cache
           $cacheKey = 'page_content_' . $page;
           $this->cache->delete($cacheKey);
           return true;
       }
       return false;
    }

Additional Resources

To deepen your understanding and explore more advanced features, consider the following resources: