Tuesday, September 18th, 2007...11:42 am

Easy XML Generation with Ruby and ERB

Jump to Comments

Ah, the bracket tax. XML-Situps, whatever you want to call them, if you write any code (especially if you use java) it should be your sworn enemy. The other day I had to create about 40 XML files for some Java Webstart configs. Instead of doing them by hand, I decided to check out the ERB class for easy "templating" that's built into Ruby. It makes doing code generation very easy!

Our Task

Create a custom XML file for each .jar file in a directory.

...more after the jump!

Strategy

With any code generation project we need to identify a few things.

  1. Static template: The code/text that will stay the same.
  2. Dynamic variables: These are the pieces that will change in our template.
  3. Input source: The input for our dynamic variables.

For me, the static template is a JNLP XML file that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<jnlp
  spec="1.0+"
  codebase="http://redacted.com/libs/com.redacted.dao_1.3"
  href="RcpWebStart.jnlp">

  <information>
    <title>RedactedLibs</title>
    <vendor>Redacted</vendor>
    <description><strong>com.redacted.dao_1.3.1.jar</strong></description>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
      <j2se version="1.4+"/>
      <jar href="com.redacted.dao_1.3.1.jar"/>
  </resources>
  <component-desc/>
</jnlp>

The dynamic bits will be anywhere a jar is referenced as well as the directory for the jar.

The Template

The template defines our static text, and the variables and/or ruby code that will be substituted into the text by ERB.

We use

<%= %>

blocks to substitute in variables from our ruby script.

Here's what my template looks like, I have four variables that I want to substitute in.

  • basedir: the base directory of where I'm storing my jar.
  • shortname: the name of the jar sans the version.
  • jarname: the full name of the jar file.
<?xml version="1.0" encoding="UTF-8"?>
<jnlp
  spec="1.0+"
  codebase="http://redacted.net/libs/<%=basedir%>/<%=shortname%>_<%=version%>"
  href="RcpWebStart.jnlp">
  <information>
    <title><%=basedir%></title>
    <vendor>Redacted</vendor>
    <description><%=jarname%></description>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
      <j2se version="1.4+"/>
      <jar href="<%=jarname%>
"/>
  </resources>
  <component-desc/>
</jnlp>

The Code

Let's examine some code. The first thing I do is create the template string, i Use the %q{ shortcut to avoid having to worry about quotes.

Next, I need to define my template, that's what this line does.

jnlp = ERB.new(template, 0, "%<>");

It says, create a new ERB object using the string 'template' as the template and the "%<>" as my template escape operators.

Our next step is to loop through all the jar's in a directory and create a new file for each one, here's the juicy bits to accomplish that.

b = binding
     jarname = File.basename(path)
     basedir = ARGV[1]
     shortname = jarname.gsub(/(.+?)_(\d.+)$/,'\1')
     version = jarname.gsub(/(.+?)_(\d\.\d)(.+)$/,'\2')

     dirname = shortname + "_" + version
     ourfile = jnlp.result(b)

Notice that we create a binding variable called b and then pass that to our ERB object (jnlp), that binding variable tells ERB to use template variables in the same scope as it. It's some ruby black magic that makes our lives easier.
The rest of the script actually creates some directory and writes the created file to a directory based on the name of the jar.

require "erb"
require "find"

template = %q{
<?xml version="1.0" encoding="UTF-8"?>
<jnlp
  spec="1.0+"
  codebase="http://redacted.net/libs/<%=basedir%>/<%=shortname%>_<%=version%>"
  href="RcpWebStart.jnlp">
  <information>
    <title><%=basedir%></title>
    <vendor>Redactedl</vendor>
    <description><%=jarname%></description>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
      <j2se version="1.4+"/>
      <jar href="<%=jarname%>"/>
  </resources>
  <component-desc/>
</jnlp>}

  jnlp = ERB.new(template, 0, "%<>");

  #Search for jar files here
  Find.find(ARGV[0]) do |path|

   if !FileTest.directory?(path)
     b = binding
     jarname = File.basename(path)
     basedir = ARGV[1]
     shortname = jarname.gsub(/(.+?)_(\d.+)$/,'\1')
     version = jarname.gsub(/(.+?)_(\d\.\d)(.+)$/,'\2')

     dirname = shortname + "_" + version

     #pass in the binding variable and create a string from the template
     ourfile = jnlp.result(b)

     #create a new directory and file, and output the template string to it.
     Dir.mkdir(basedir + "/" + dirname)
     outfile = File.new(basedir + "/" + dirname +  "/RcpWebStart.jnlp","w+");
     outfile <<ourfile
   end

The fun doesn't stop there

This is an elementary example, but ERB also allows you to embed real ruby code in the template, this allows you to accomplish looping.

So we could pass in an array of person objects and loop through them with a template like this:

<people>
   <%@people.each do |person| %>
   <name><%=person.name%></name>
  <%end%>
 </people>

1 Comment

Leave a Reply