Hello,
It's Friday. Ruby Quiz time! [1] Join us for the second challenge in
the new year in 2020! Here we go:
Challenge #17 - Build an HTML Template Engine Like It's 1999 [2]
Remember the time before Facebook? It was all about blogs
and building your own newsfeed from blog rolls.
The most popular newsfeed tool in 1999 was called Planet
and used the HTML template language / engine.
Let's say you have the following blog roll / channels
defined in ruby:
Blogroll = Struct.new( :name, :owner_name, :date_822, :channels )
Channel = Struct.new( :name, :url, :channel_link )
blogroll = Blogroll.new( 'OpenStreetMap Blogs',
'OpenStreetMap',
Date.new( 2020, 2, 7 ).rfc2822,
[Channel.new( 'Shaun McDonald',
'http://blog.shaunmcdonald.me.uk/feed/',
'http://blog.shaunmcdonald.me.uk/' ),
Channel.new( 'Mapbox',
'https://blog.mapbox.com/feed/tagged/openstreetmap/',
'https://blog.mapbox.com/' ),
Channel.new( 'Mapillary',
'https://blog.mapillary.com/rss.xml',
'https://blog.mapillary.com' ),
Channel.new( 'Richard Fairhurst',
'http://blog.systemed.net/rss',
'http://blog.systemed.net/' )]
)
pp blogroll
pretty printing to:
#<struct Blogroll
name="OpenStreetMap Blogs",
owner_name="OpenStreetMap",
date_822="Fri, 7 Feb 2020 00:00:00 +0000",
channels=
[#<struct Channel
name="Shaun McDonald",
url="http://blog.shaunmcdonald.me.uk/feed/",
channel_link="http://blog.shaunmcdonald.me.uk/">,
#<struct Channel
name="Mapbox",
url="https://blog.mapbox.com/feed/tagged/openstreetmap/",
channel_link="https://blog.mapbox.com/">,
#<struct Channel
name="Mapillary",
url="https://blog.mapillary.com/rss.xml",
channel_link="https://blog.mapillary.com">,
#<struct Channel
name="Richard Fairhurst",
url="http://blog.systemed.net/rss",
channel_link="http://blog.systemed.net/">]>
Let's build an HTML template engine like it's 1999.
Example:
<?xml version="1.0"?>
<opml version="1.1">
<head>
<title><TMPL_VAR name></title>
<dateModified><TMPL_VAR date_822></dateModified>
<ownerName><TMPL_VAR owner_name></ownerName>
</head>
<body>
<TMPL_LOOP channels>
<outline type="rss"
text="<TMPL_VAR name>"
xmlUrl="<TMPL_VAR url>"
<TMPL_IF channel_link> htmlUrl="<TMPL_VAR
channel_link>"</TMPL_IF> />
</TMPL_LOOP>
</body>
</opml>
In the starter level handle variables, conditionals, and loops.
Syntax overview:
**1) `<TMPL_VAR identifier>`**
<TMPL_VAR name>
<TMPL_VAR date_822>
**2) `<TMPL_IF identifier>..</TMPL_IF>`**
<TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link>" </TMPL_IF>
**3) `<TMPL_LOOP identifier>..</TMPL_LOOP>`**
<TMPL_LOOP channels>
<outline type="rss"
text="<TMPL_VAR name>"
xmlUrl="<TMPL_VAR url>"
<TMPL_IF channel_link> htmlUrl="<TMPL_VAR
channel_link>"</TMPL_IF> />
</TMPL_LOOP>
Note: Inside loops you have
to auto-add the loop context e.g. `channel` to make
variables or conditionals work e.g. `name`
becomes `[channel.]name` and `channel_link` becomes `[channel.]channel_link`
and so on.
The challenge: Code a `merge`
method that merges the passed in HTML template
and the blogroll into a merged XML document
that passes the RubyQuizTest :-).
def merge( template, blogroll )
# ...
end
Resulting in:
<?xml version="1.0"?>
<opml version="1.1">
<head>
<title>OpenStreetMap Blogs</title>
<dateModified>Fri, 7 Feb 2020 00:00:00 +0000</dateModified>
<ownerName>OpenStreetMap</ownerName>
</head>
<body>
<outline type="rss"
text="Shaun McDonald"
xmlUrl="http://blog.shaunmcdonald.me.uk/feed/"
htmlUrl="http://blog.shaunmcdonald.me.uk/" />
<outline type="rss"
text="Mapbox"
xmlUrl="https://blog.mapbox.com/feed/tagged/openstreetmap/"
htmlUrl="https://blog.mapbox.com/" />
<outline type="rss"
text="Mapillary"
xmlUrl="https://blog.mapillary.com/rss.xml"
htmlUrl="https://blog.mapillary.com" />
<outline type="rss"
text="Richard Fairhurst"
xmlUrl="http://blog.systemed.net/rss"
htmlUrl="http://blog.systemed.net/" />
</body>
</opml>
Note: For the test the xml document gets automatically
reformatted
and pretty printed in compact style with two-space indent
so you do NOT have to worry about spaces, line breaks, quotes or indentation.
Reformatted example:
<opml version='1.1'>
<head>
<title>OpenStreetMap Blogs</title>
<dateModified>Fri, 7 Feb 2020 00:00:00 +0000</dateModified>
<ownerName>OpenStreetMap</ownerName>
</head>
<body>
<outline type='rss' text='Shaun McDonald'
xmlUrl='http://blog.shaunmcdonald.me.uk/feed/'
htmlUrl='http://blog.shaunmcdonald.me.uk/'/>
<outline type='rss' text='Mapbox'
xmlUrl='https://blog.mapbox.com/feed/tagged/openstreetmap/'
htmlUrl='https://blog.mapbox.com/'/>
<outline type='rss' text='Mapillary'
xmlUrl='https://blog.mapillary.com/rss.xml'
htmlUrl='https://blog.mapillary.com'/>
<outline type='rss' text='Richard Fairhurst'
xmlUrl='http://blog.systemed.net/rss'
htmlUrl='http://blog.systemed.net/'/>
</body>
</opml>
To qualify for solving the code challenge / puzzle you must pass the test:
require 'minitest/autorun'
class RubyQuizTest < MiniTest::Test
Blogroll = Struct.new( :name, :owner_name, :date_822, :channels )
Channel = Struct.new( :name, :url, :channel_link )
def blogroll
Blogroll.new( 'OpenStreetMap Blogs',
'OpenStreetMap',
Date.new( 2020, 2, 7 ).rfc2822,
[Channel.new( 'Shaun McDonald',
'http://blog.shaunmcdonald.me.uk/feed/',
'http://blog.shaunmcdonald.me.uk/' ),
Channel.new( 'Mapbox',
'https://blog.mapbox.com/feed/tagged/openstreetmap/',
'https://blog.mapbox.com/' ),
Channel.new( 'Mapillary',
'https://blog.mapillary.com/rss.xml',
'https://blog.mapillary.com' ),
Channel.new( 'Richard Fairhurst',
'http://blog.systemed.net/rss',
'http://blog.systemed.net/' )]
)
end
def test_merge
template = File.open( "./opml/opml.xml.tmpl", "r:utf-8" ).read
xml = File.open( "./opml/opml.xml", "r:utf-8" ).read
assert_equal prettify_xml( xml ),
prettify_xml( merge( template, blogroll ))
end
## xml helper
def prettify_xml( xml )
d = REXML::Document.new( xml )
formatter = REXML::Formatters::Pretty.new( 2 ) # indent=2
formatter.compact = true # This is the magic line that does what you need!
formatter.write( d.root, '' )
end
end
Start from scratch or, yes, use any library / gem you can find.
Post your code snippets on the "official" Ruby Quiz Channel,
that is, ruby-talk right here.
Happy text processing and template merging with Ruby.
[1] https://github.com/planetruby/quiz
[2] https://github.com/planetruby/quiz/tree/master/017