#/usr/bin/ruby
require 'html5'
unless ARGV.first =~ /.tmpl$/ and ARGV.length == 1
STDERR.puts "Usage: ruby #{__FILE__} index.html.tmpl"
exit 86
end
tmpl = open(ARGV.first) {|file| file.read}
# convert variable names to xpath expressions
xpath = proc do |name|
case name
when 'Channels' then 'planet:source'
when 'Items' then 'atom:entry'
when 'channel_link' then "atom:source/atom:link[@rel='alternate']/@href"
when 'channel_title' then 'atom:source/atom:title'
when 'channel_title_plain' then 'atom:title/text()'
when 'date' then 'atom:updated/@planet:format'
when 'feed' then "atom:link[@rel='self']/@href"
when 'feedtype' then "atom:link[@rel='self']/@type"
when 'generator' then 'atom:generator'
when 'link' then "atom:link[@rel='alternate']/@href"
when 'name' then 'atom:title'
when 'owner_name' then 'atom:author/atom:name'
when 'title' then 'atom:title'
when 'url' then "atom:link[@rel='alternate']/@href"
when /^channel_/ then "atom:source/planet:#{name.split('_',2).last}"
when 'new_date' then
"substring-before(atom:updated/@planet:format,', ')}, {" +
"substring-before(substring-after(atom:updated/@planet:format,', '), ' ')"
else
STDERR.puts "Unknown variable encountered: #{name}"
name
end
end
# kill the DOCTYPE
tmpl.sub! /\s*/, ''
# enclose line comments in XML/HTML comments
tmpl.gsub! /^((#.*\n)+)/, "\n"
# special case: feed type already is formatted as a mime type
tmpl.gsub! 'application/+xml', ''
# template variables
tmpl.gsub! //, ''
tmpl.gsub! // do
if $1 == 'content'
''
else
"{#{xpath.call($1)}}"
end
end
# template if statements temporarily become pseudo-span elements
tmpl.gsub! /<\/TMPL_IF>/, ''
tmpl.gsub! // do
if $1 == 'new_date'
''
elsif $1 == 'new_channel'
''
else
""
end
end
# template loop statements temporarily become pseudo-span elements
tmpl.gsub! /<\/TMPL_LOOP>/, ''
tmpl.gsub! // do
""
end
# convert template to DOM
doc = HTML5.parse(tmpl)
# reparent conditional head elements
doc.elements.each('html/body/span[meta or link]') {|span|
doc.elements['html/head'] << span
}
doc.elements.each('//*') do |node|
# pseudo span elements become xsl control elements
if node.name == 'span'
if node.attributes['select']
node.name = 'xsl:for-each'
elsif node.attributes['test']
node.name = 'xsl:if'
end
end
# choose between content and summary
if node.name == 'choose'
node.name = 'xsl:choose'
child = node.add_element('xsl:when', 'test' => 'atom:content')
child.add_element('xsl:apply-templates', 'select' => 'atom:content')
child = node.add_element('xsl:when', 'test' => 'atom:summary')
child.add_element('xsl:apply-templates', 'select' => 'atom:summary')
end
# convert scripts to CDATA
if node.name == 'script'
node.texts.each do |text|
if text.value =~ /[&<]/
text.previous_sibling = REXML::CData.new(text.value)
text.remove
end
end
end
# add namespace to html element
if node.name == 'html'
node.add_namespace 'http://www.w3.org/1999/xhtml'
end
# replace string interpretation in text nodes with xsl:value of elements
node.texts.each do |text|
if text.value =~ /\{.*?\}/
text.value.split(/\{(.*?)\}/).each_with_index do |value, index|
if index % 2 == 1
text.previous_sibling = REXML::Element.new('xsl:value-of')
text.previous_sibling.attributes['select'] = value
elsif !value.empty?
text.previous_sibling = REXML::Text.new(value)
end
end
text.remove
end
end
end
# Egregious hack: convert all escaped single quotes in XPath
# expressions to double quotes.
tmpl = doc.to_s.gsub /\{.*?\}| (test|select)='.*?'/ do |xpath|
xpath.gsub(''','"')
end
# wrapt the template in a stylesheet
xslt = REXML::Document.new <
#{tmpl.gsub(/\n\n\n+/,"\n\n")}
content
EOF
# output stylesheet
open(ARGV.first.sub(/.tmpl$/,'.xslt'),'w') do |file|
file.write xslt.to_s.gsub('"','"')
end