Read The Times Australia

Daily Bulletin

Converting HTML to Markdown with Upmark

  • Written by: Josh Bassett, Data Platform Technical Lead, The Conversation

Here at The Conversation we run a Job Board that requires parsing a whole bunch of job descriptions in HTML and converting them to Markdown. When we originally built the Job Board we looked around for a HTML to Markdown converter library written in Ruby but unfortunately we couldn’t find one, so we built our own: Upmark.

Upmark allows you to easily convert HTML documents to Markdown format:

require "upmark"
html = "<p>messenger <strong>bag</strong> skateboard</p>"
markdown = Upmark.convert(html)
puts markdown
"messenger **bag** skateboard"

It can handle most HTML tags and anything that isn’t able to be converted to Markdown is passed through as HTML.

How does it work?

Upmark does all the heavy lifting using a parser transformer built using the excellent Parslet library. Parslet allows you to define a grammar in plain ruby that is used to parse a document into a syntax tree. The syntax tree can then be arbitrarily transformed, in our case it is transformed into a Markdown document.

The whole process looks something like this:

image Author provided Parse it! The first phase of the process is parsing the input into a syntax tree. To parse a HTML document we first need to define a grammar. A grammar contains the individual rules for parsing the different parts of a document. Rules for parsing simpler elements can be combined together to parse more complex structures. Parslet provides us with the Parslet::Parser class which we extend to define parser: class MyParser < Parslet::Parser # all the rules go here end In the case of Upmark, we first define rules for parsing the more complex parts of a HTML document, like an element. The rule for parsing an element is then decomposed into rules for parsing tags and attributes. These rules are then further broken down into combinations of simpler rules for text, numbers, and whitespace. Consider the following snippet of HTML: <p>hello world!</p> <img src="lol.gif" /> <ol> <li>one</li> <li>two</li> <li>three</li> </ol> This document is just a series of HTML elements, so the first rule we define might be: rule(:element) do start_tag.as(:start_tag) >> # e.g. "<p>" children.as(:children) >> end_tag.as(:end_tag) # e.g. "</p>" end This rule says that in order to parse an element we need a start_tag, some children, and finally an end_tag. The as modifiers define how they are labelled in the resulting syntax tree. Okay, so now what? Let’s break it down further and add the next rule to our parser. To parse a start_tag we need a < character, a name, zero or more attributes (separated by whitespace), some optional whitespace, and finally a > character. rule(:start_tag) do str('<') >> name.as(:name) >> (space >> attribute).repeat.as(:attributes) >> space? >> str('>') end According to the XML spec, a name is just a string limited to a particular range of characters: rule(:name) do match(/[a-zA-Z_:]/) >> match(/[\w:\.-]/).repeat end Here are the rules for parsing whitespace: rule(:space) { match(/\s/).repeat(1) } rule(:space?) { space.maybe } I’ll leave defining the rules for parsing children and attributes as an exercise for the reader (or you can cheat and just look in the Upmark source code). Finally, this is how we apply our parser to the input: tree = MyParser.new.parse(html) Once our parser is applied to a document, a syntax tree is generated. Transform it! The second phase of the whole process is to transform the syntax tree into some desired output. Parslet syntax trees are represented as an array of nested hashes. For example: tree = [ { element: { name: "img", attributes: [{name: "src", value: "http://example.com/lol.gif"}], children: [] } } ] Given the above syntax tree, let’s write a transform which traverses the syntax tree and converts it to Markdown. Again, Parslet makes transforming easier for us by providing the Parslet::Transform class to extend: class MyTransform < Parslet::Transform rule( element: { name: "img", attributes: subtree(:attributes) } ) do |img| src = img[:attributes].find {|attribute| attribute["name"] == "src" }["value"] "![](#{src})" end end The MyTransform transform matches an img element with a subtree of attributes. It then plucks out the src attribute and returns the Markdown for an image. This is how we apply the transform to the syntax tree: markdown = MyTransform.new.apply(tree) puts markdown "![](http://example.com/lol.gif)" Turtles all the way down So how did we write a parser that converts an entire HTML document to Markdown? The answer is simple: it’s turtles all the way down. By combining multiple rules and transforms, we can break a big problem down into a series of smaller problems. Hopefully this gives you some insight into how to write your own parser using Parslet, and if you happen to need a handy HTML to Markdown converter then please check out Upmark.

Authors: Josh Bassett, Data Platform Technical Lead, The Conversation

Read more http://theconversation.com/converting-html-to-markdown-with-upmark-65788

Business News

Why Choosing the Right Bollard Supplier Matters for Australian Businesses and Public Spaces

From busy CBD streetscapes to sprawling warehouse loading docks, bollards have become one of the most essential safety and security fixtures across Australia. Whether protecting pedestrians from veh...

Daily Bulletin - avatar Daily Bulletin

Why Modular Content Is Transforming Modern Marketing Teams

Modern marketing teams are expected to produce more content than ever before. They need to support websites, landing pages, email campaigns, social channels, product pages, sales enablement material...

Daily Bulletin - avatar Daily Bulletin

Everything You Need to Know About Getting Support from Optus

Whether you've been an Optus customer for years or you've just switched over, at some point you'll probably need to contact their support team. Maybe your bill looks different from what you expected. ...

Daily Bulletin - avatar Daily Bulletin

The Marketing Strategy That’s Quietly Draining Sydney Business Owners’ Bank Accounts

Sydney businesses are investing more in digital marketing than ever before. The intention is clear. More visibility should mean more leads, more customers, and steady growth. However, many business ...

Daily Bulletin - avatar Daily Bulletin

Why Mining Hose Solutions Are Essential For High-Performance Industrial Operations

In environments where the ground itself is constantly shifting, breaking, and being reshaped, every component must be built to endure. Mining operations are among the most demanding in the industria...

Daily Bulletin - avatar Daily Bulletin

The Reason Talented Teams Underperform

If you’re in business, you might have seen it before. A team of capable and smart people just suddenly slows down, and things start spiraling out of control. On paper, everything looks perfect, but ...

Daily Bulletin - avatar Daily Bulletin

Why More Aussie Tradies Are Moving Away From Paid Ads

Across Australia, a lot of tradies are busy. There’s no shortage of demand in industries like plumbing, electrical, landscaping, and building. But being busy doesn’t always mean running a smooth or...

Daily Bulletin - avatar Daily Bulletin

Why Careers In The Defence Industry Are Growing Rapidly

The defence sector has evolved far beyond traditional roles, opening doors to a wide range of opportunities across technology, engineering, intelligence, and operations. This is where defense industry...

Daily Bulletin - avatar Daily Bulletin

Strategic partnerships to enable global acceleration for Aussie fashion brands: SHEIN Xcelerator launches

SHEIN Xcelerator is introducing a more agile, demand-led operating model, allowing brands to scale while retaining control over creative direction and identity. For fashion brands, the pressure t...

Daily Bulletin - avatar Daily Bulletin

The Daily Magazine

Australia’s Best Walking Trails and the Shoes You Need to Tackle Them

Australia is not short on spectacular walks. You can follow ocean cliffs in Victoria, cross ancien...

Why Pre-Purchase Building Inspections Are Essential Before Buying a Home in Australia

source Have you ever walked through an open home and started picturing your furniture, family d...

5 Signs Your Car Needs Immediate Attention Before It Breaks Down

Car problems rarely appear without warning. In most cases, your vehicle gives clear signals before...

Ensuring Safety and Efficiency with Professional Electrical Solutions

For businesses in Newcastle, a safe and fully functioning workplace remains a key part of day-to-d...

Choosing The Right Bin Hire Solution For Hassle-Free Waste Management

When it comes to managing waste efficiently, finding the right solution can save both time and eff...

Why Cleanliness Is Critical In Childcare Environments

Children explore the world with curiosity, often touching surfaces, sharing toys, and interacting ...

What to Look for in a Reliable Australian Engineering Partner

Choosing an engineering partner is rarely just about technical capability. Most businesses can fin...

How to Choose a Funeral Home That Supports Families with Care

Choosing a funeral home is rarely something families do under ideal circumstances. It often happen...

Why Premium Coffee Matters in Modern Hospitality Venues

In hospitality, details shape perception long before a guest consciously evaluates them.  Lightin...