0
\$\begingroup\$

I wrote a simple script to translate (a subset of) StackExchange markdown to HTML. I used Regex for this task, but luckily I only had to use simple patterns.

Modularization

I also wrote a Regexp-generating function as I noticed that in Markdown you are always looking for things inside such and such symbols, I feel like it simplified the code.

What I do not like

The massive and mindless use of .tr to remove surrounding mark-up before applying HTML tags.

Tests

The code has 9 testcases, one for each feature.

Code / Tests ratio.

The code is pretty compact, totalling 16 lines of logic supported by 50 blank/end-only/comments/tests lines.

Testing bug

I used my own testing framework, but sadly it has a bug that does not allow to use # inside tests, so I replaced it with 35.chr

Links trickiness

The code is a bit longer when handling HTML links as they are a bit more complex than tags like bold or italics

Limitations

The code does not escape tags contained in:

``

It also has no support for lists of any kind and gives results visually more ugly (still correct) than the markdown implemented on this site.

It works on my basic example.

(On-topic, as I acknowledge that this code is not perfect but I do not ask you to fix it.)

The code

require "arrow_test"

# Returns a Regex that matches anything inside the delimiter.
# The given delimiter is escaped to allow the use of special characters.
#
# anything_inside("**") #=> /\*\*.*\*\*/
def anything_inside(start, ending=nil)
  start = "\\" + start.chars.join("\\")
  ending = start if ending == nil
  Regexp.new(start + ".*" + ending)
end

# This function translates MarkDown to HTML.
#
# markdown_to_html("plaintext") #=> "plaintext"
# markdown_to_html("**bold only**") #=> "<b>bold only</b>"
# markdown_to_html("_italics only_") #=> "<i>italics only</i>"
# markdown_to_html("`code only`") #=> "<code>code only</code>"
# markdown_to_html("    code by indentation") #=> "<code>code by indentation</code>"
# markdown_to_html("> To be or not to be\n\n") #=> "<q> To be or not to be</q>"
# markdown_to_html(35.chr + "Title\n") #=> "<h1>Title</h1>"
# markdown_to_html("\npara\n") #=> "\n<p>para</p>\n"
# markdown_to_html("[foo](http://foo.com)") #=> "<a href=\"http://foo.com\">foo</a>"
def markdown_to_html(text)
  text
    .gsub(anything_inside(">", "\n\n"))  {|txt| "<q>#{txt.sub(/^>*/,"").tr("\n\n", "")}</q>" }
    .gsub(anything_inside("**")) {|txt| "<b>#{txt.tr("**","")}</b>"}
    .gsub(anything_inside("_")) {|txt| "<i>#{txt.tr("_","")}</i>"}
    .gsub(anything_inside("`"))  {|txt| "<code>#{txt.tr("`","")}</code>"}
    .gsub(anything_inside("#", "\n")) {|txt| "<h1>#{txt.tr("\n","").sub(/^\#*/,"")}</h1>"}
    .gsub(anything_inside("\n")) {|txt| "\n<p>#{txt.tr("\n","")}</p>\n"}
    .gsub(/\ \ \ \ .*/) {|txt| "<code>#{txt.strip}</code>"}
    .gsub(/\[.*\]\(.*\)/) do |txt|
       to_show = txt.match(/(\[.*\])/)[1].tr("[","").tr("]","")
       url = txt.match(/(\(.*\))/)[1].tr("(","").tr(")","")
       "<a href=\"#{url}\">#{to_show}</a>"
    end
end


if __FILE__ == $0
  arrow_test($0, true)
  File.write("example.html", markdown_to_html("""
# A nice example.

All the time, the site [example](www.example.com) amuses me with its beauty.

I felt a **strong attachment** to it, regardless of its simplicity.

The site is an  _example_ as few things can be, as Lapalisse said:

> What is an example, is an example.


Such beatiful a site may be used in examples such as:

**Python**

    import webbrowser
    webbrowser.open(\"www.example.com\")

And now, for something completely different, `inline code`.

> The great man may not have ideas, but will always write examples.


_And that is all,_ I hope you enjoyed reading this example, good day.

"""))
end
\$\endgroup\$
1
  • \$\begingroup\$ FYI: ending = start if ending == nil can be rewritten ending ||= start. \$\endgroup\$
    – Jonah
    Sep 21, 2015 at 3:10

1 Answer 1

2
\$\begingroup\$

The whole approach of using regexes to parse Markdown and convert it into HTML is broken.

  1. You have no way of knowing whether any html you generate with one call to gsub is violating the constraints of the HTML containing it.
  2. After the first gsub call, you iteratively apply it to text which now contains some html. You may mistakenly replace parts of an html element with an html element.

Some of the individual gsub calls are fragile in themselves but none of that compares to the basic flaws of the approach.

Having said that, the way you are using regexes is duplicating code. You have typed each matched pattern twice: first in the regex, then in the closure. This would not be necessary if you used capture groups and back-references.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.