Implementing custom Markdown parser with the MD Library

Thank you very much for the options you gave me to think about. I will tak a look to them for sure.

Yesterday night I made it work and it seems that it matches the approach you suggest here:

My code:

defmodule MasWeb.MdParser.Heex do

  alias Md.Parser.State

  @behaviour Md.Parser

  @impl true
  def parse(input, state \\ %State{})
  def parse(input, state) do
    # @TODO handle the case for a tag without attributes:
    #       * <.whatever/>
    #       * <.whatever>content</.whatever>
    [tag, rest] = String.split(input, " ", parts: 2)
    [content, rest] = String.split(rest, "</.#{tag}>", parts: 2)
    [attrs, content] = String.split(content, ">", parts: 2)

    # parses markdown inside an HEEX tag to HTML and then rebuild the HEEX tag
    # to allow for the Phoenix.LiveView.HTMLEngine to the parse it as a regular 
    # *.heex file, thus keeping all the niceties for LiveView tracking? (need to
    # double check my assumption)
    html = Md.Parser.generate(content)
    html = "<.#{tag} #{attrs}>#{html}</.#{tag}>"

    {rest, %State{state | ast: [html | state.ast]}}
  end
end

The custom Phoenix Engine:

defmodule MasWeb.PhoenixEngine do

   # @link Inspired by: https://github.com/boydm/phoenix_markdown/blob/ef7b5f76f339babec688021080a70708d9ddf1c1/lib/phoenix_markdown/engine.ex#L22

  @moduledoc """
  a single public function (compile) that Phoenix uses to compile incoming templates. You should not need to call it yourself.
  """

  @behaviour Phoenix.Template.Engine

  @doc """
  Callback implementation for `Phoenix.Template.Engine.compile/2`

  Precompiles the String file_path into a function defintion, using the EEx and Earmark engines

  The compile function is typically called for by Phoenix's html engine and isn't something
  you need to call your self.

  ### Parameters
    * `path` path to the template being compiled
    * `name` name of the template being compiled

  """
  def compile(path, _name) do

    options = [
      engine: Phoenix.LiveView.TagEngine,
      file: path,
      line: 1,
      caller: __ENV__,
      source: "",
      tag_handler: Phoenix.LiveView.HTMLEngine
    ]

    path
    |> File.read!()
    |> Md.generate(Md.Parser.Default, format: :none)
    |> EEx.compile_string(options)
  end
end

The config.exs:

config :phoenix, :template_engines,
  # will handle all markdown files that have an extension *.html.md, e,g. test.html.md
  md: MasWeb.PhoenixEngine

config :md, syntax: %{
  custom: [{"<.", {MasWeb.MdParser.Heex, %{}}}]
}

Add md extension to Phoenix live reload in config/dev.exs

config :mas, MasWeb.Endpoint,
live_reload: [
  patterns: [
    ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
    ~r"priv/gettext/.*(po)$",
    ~r"lib/mas_web/(controllers|live|components)/.*(ex|heex|md)$"
  ]
]

The markdown file test.html.md:

## TEST

<.horizontal_left_card class="bg-transparent" image_path="/images/svg/storyset_mobile-encryption-amico.svg">
One of the key challenges that mobile developers face when it comes to securing their apps and APIs is the ability to think like an attacker. This is because attackers approach mobile app and API security from a different perspective and mindset than developers. They usually combined several techniques and chain the weaknesses and vulnerabilities of the mobile apps and their APIS to succeed on their intents.
Developers are typically focused on building functionality and features that meet user requirements, while also ensuring that the app is performant and easy to use. While security is certainly an important consideration, it is often not the primary focus of developers, who may not have a deep understanding of the various security risks that their app or API may face, and if they do they may not be aware how creative hackers can be on combining such security risks to mount a successful attack.
</.horizontal_left_card>

<.horizontal_right_card class="w-full" image_path="/images/svg/storyset_hacker-bro.svg">
In contrast, attackers are motivated by different factors, such as financial gain, political, ideological or social motives, or simply the challenge of exploiting vulnerabilities in the mobile and their APIs. They approach mobile app security and API security from a different perspective, actively seeking out weaknesses and vulnerabilities that they can exploit for their own purposes or who they work for, that can be a criminal organization, a state or just a company trying to get ahead of their competitors.
To be able to effectively secure a mobile app or API, it is therefore important for developers to be able to think like an attacker. This requires a deep understanding of the various techniques and tools that attackers use to exploit vulnerabilities in mobile apps and APIs, as well as the ability to anticipate potential attack vectors and design security controls that can mitigate these risks.
</.horizontal_right_card>

<.cta_newsletter ></.cta_newsletter>

The result:

At the moment my Phoenix 1.7 app isn’t using LiveView but I plan to do so, therefore I need to wait until I can be sure that this also works properly with LiveView tracking.

Would this be something you would consider to add support for in your Lib? If yes I can make the PR.

4 Likes