commonmark-hiccup
commonmark-hiccup copied to clipboard
Configurable Clojure library for converting markdown to HTML.
commonmark-hiccup
A small Clojure library for converting CommonMark markdown to HTML. It is designed to make the HTML output as configurable as possible, relying on Hiccup as an intermediary representation.
Installation
Add the following dependency to project.clj:
[commonmark-hiccup "0.2.0"]
Documentation
commonmark-hiccup uses the commonmark-java parser and implements its own renderer, which transforms the CommonMark AST to Hiccup-compatible Clojure data structures. It then uses Hiccup to render them to their final HTML representation.
commonmark-hiccup is built for configurability, not performance. I use it to render static content for different web sites, each with different requirements for how paragraphs, code blocks etc. should look like.
Usage
You can convert a markdown string to HTML using markdown->html:
user=> (require '[commonmark-hiccup.core :refer [markdown->html]])
nil
user=> (markdown->html "This is a *test*.")
"<p>This is a <em>test</em>.</p>"
You can pass a configuration to the converter to tweak the output. This example
renders paragraphs without the surrounding <p></p> tags:
user=> (let [config (update-in commonmark-hiccup.core/default-config
[:renderer :nodes org.commonmark.node.Paragraph]
(constantly :content))]
(markdown->html config "This is a *test*."))
"This is a <em>test</em>."
Configuration
The default configuration defines the Hiccup snippets to which the different CommonMark AST nodes are rendered:
(def default-config
{:renderer {:nodes {org.commonmark.node.Document :content
org.commonmark.node.Heading ['(:h :node-level) :content]
org.commonmark.node.Paragraph [:p :content]
org.commonmark.node.Text :node-literal
org.commonmark.node.BulletList [:ul :content]
org.commonmark.node.OrderedList [:ol {:start :node-startNumber} :content]
org.commonmark.node.ListItem [:li :content]
org.commonmark.node.BlockQuote [:blockquote :content]
org.commonmark.node.HtmlBlock :node-literal
org.commonmark.node.HtmlInline :node-literal
org.commonmark.node.FencedCodeBlock [:pre [:code {:class :node-info} :node-literal]]
org.commonmark.node.IndentedCodeBlock [:pre [:code :node-literal]]
org.commonmark.node.Code [:code :node-literal]
org.commonmark.node.Link [:a {:href :node-destination} :content]
org.commonmark.node.Image [:img {:src :node-destination
:alt :text-content
:title :node-title}]
org.commonmark.node.Emphasis [:em :content]
org.commonmark.node.StrongEmphasis [:strong :content]
org.commonmark.node.ThematicBreak [:hr]
org.commonmark.node.SoftLineBreak " "
org.commonmark.node.HardLineBreak [:br]}}
:parser {:extensions nil}})
The :nodes map uses commonmark-java node classes as keys. The values are
just Clojure data structures. Some keywords and lists are replaced during
rendering:
- All keywords prefixed with
:node-are replaced with the respective property of the rendered node (e.g.:node-literalfororg.commonmark.node.HtmlBlockis replaced with the value returned byHtmlBlock::getLiteral). - Some keywords are special:
:contentis replaced with the rendered content of the current node's children;:text-contentis replaced with the concatenated content of allorg.commonmark.node.Textchild nodes. - List elements are joined to strings. This is useful for rendering node
properties as part of a longer string.
['(:h :node-level) :content]uses thelevelproperty of theHeadingnode to render the appropriate HTML tag (e.g.<h1></h1>for level 1 headings).
For the available properties for each node type, refer to the commonmark-java sources.
Extensions
CommonMark extensions
can be added to the parser by including them in the [:parser :extensions]
list.
For example, to add the support for GFM tables:
- Add the dependency to your project
[com.atlassian.commonmark/commonmark-ext-gfm-tables "..."]
- Add the extension to your config along with the new renderers
(require '[commonmark-hiccup.core :as md])
(def my-config
(-> md/default-config
(update-in [:parser :extensions] conj
(org.commonmark.ext.gfm.tables.TablesExtension/create))
(update-in [:renderer :nodes] merge
{org.commonmark.ext.gfm.tables.TableBlock [:table :content]
org.commonmark.ext.gfm.tables.TableHead [:thead :content]
org.commonmark.ext.gfm.tables.TableBody [:tbody :content]
org.commonmark.ext.gfm.tables.TableRow [:tr :content]
org.commonmark.ext.gfm.tables.TableCell [:td :content]})))
(md/markdown->hiccup mt-config "|head1|head2|
|---|---|
|foo|bar|")
=> ([:table
([:thead ([:tr ([:td ("head1")] [:td ("head2")])])]
[:tbody ([:tr ([:td ("foo")] [:td ("bar")])])])])
License
Copyright © 2017 Axel Schüssler
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.