I took a couple of quick shots of the group:
and the grid:
More details on my xml.com blog post.
Tim Bray has better photos here.
I took a couple of quick shots of the group:
and the grid:
More details on my xml.com blog post.
Tim Bray has better photos here.
I’ve been messing around at work trying to make some automated scheduling charts (basically Gantt-like) in Ruby. I’ve implemented it a couple of times using SVG::Graph, which is close to what I need, but I end up having to rewrite a lot of methods whenever I really start using it. It occurred to me today that I might be able to co-opt a sexy Java library to do my dirty work. JFreeChart to the rescue!
As before, I’m generally amazed at how little work goes into integrating Java and JRuby these days. It’s a testament to the JRuby team and to the wealth of well-written, well-documented Java libraries out there.
Here’s some toy code that makes a simple Gantt chart and saves it as a PNG to a file:
# have jfreechart.jar in your classpath, obviously, as well as jcommon.jar
# and use a recent jruby
require 'java'
module Gantt
class Simple
include_class 'org.jfree.chart.ChartFactory'
include_class 'org.jfree.chart.ChartUtilities'
include_class 'org.jfree.chart.JFreeChart'
include_class 'org.jfree.data.gantt.Task'
include_class 'org.jfree.data.gantt.TaskSeries'
include_class 'org.jfree.data.gantt.TaskSeriesCollection'
include_class 'org.jfree.data.time.SimpleTimePeriod'
include_class 'java.lang.System'
include_class 'java.io.File'
MILLIS_IN_A_DAY = 86400000
def initialize(title="Chunky Bacon", width=700, height=400, data=[])
@width = width
@height = height
@title = title
dataset = create_sample_data() if data.empty?
@chart = create_chart(dataset)
end
def render_to_file(filename, format="png")
javafile = java.io.File.new(filename)
ChartUtilities.saveChartAsPNG(javafile, @chart, @width, @height)
end
private
def create_sample_data
# dates as milliseconds seems the easiet
now = System.currentTimeMillis
tomorrow = now + (MILLIS_IN_A_DAY * 1)
day_after_tomorrow = now + (MILLIS_IN_A_DAY * 2)
week_from_today = now + (MILLIS_IN_A_DAY * 7)
s1 = TaskSeries.new("JRuby")
s1.add(Task.new("Download JRuby",
SimpleTimePeriod.new(now, tomorrow)))
s1.add(Task.new("Write Code",
SimpleTimePeriod.new(tomorrow, day_after_tomorrow)))
s1.add(Task.new("Setup CLASSPATH",
SimpleTimePeriod.new(day_after_tomorrow, week_from_today)))
s2 = TaskSeries.new("Java")
s2.add(Task.new("Read Comics",
SimpleTimePeriod.new(now, tomorrow)))
s2.add(Task.new("Write Code",
SimpleTimePeriod.new(tomorrow, day_after_tomorrow)))
s2.add(Task.new("Setup CLASSPATH",
SimpleTimePeriod.new(day_after_tomorrow, week_from_today)))
collection = TaskSeriesCollection.new
collection.add(s1)
collection.add(s2)
return collection
end
def create_chart(dataset)
opts = {
:title => @title,
:domain_axis_label => "Task",
:range_axis_label => "Date",
:data => dataset,
:include_legend => true,
:tooltips => false,
:urls => false
}
chart = ChartFactory.createGanttChart(
opts[:title],
opts[:domain_axis_label],
opts[:range_axis_label],
opts[:data],
opts[:include_legend],
opts[:tooltips],
opts[:urls]
)
return chart
end
end # class Simple
end # module Gantt
chart = Gantt::Simple.new("Gantt Chart Demo")
puts "Rendering chart"
chart.render_to_file("simplegantt.png")
Example PNG:
Well, I finally caught up with the crowd and got JRuby running on one of my dev boxes. The reason I’d been interested in it from the getgo was because Ruby lacks any support for internal XSLT processing. All those system()s were starting to get me down, especially as I’m trying to get a DocBook->PDF rendering webservice to be a lot faster. Much to my surprise, I was able to get simple transforms working in almost no time (thanks in part to lots of help). Without further ado, here’s a simple library for XSLT transforms using either Xalan-J or Saxon (make sure you have the jars for both in your CLASSPATH):
require 'java'
module JXslt
include_class "javax.xml.transform.TransformerFactory"
include_class "javax.xml.transform.Transformer"
include_class "javax.xml.transform.stream.StreamSource"
include_class "javax.xml.transform.stream.StreamResult"
include_class "java.lang.System"
class XsltProcessor
def transform(xslt,infile,outfile)
transformer = @tf.newTransformer(StreamSource.new(xslt))
transformer.transform(StreamSource.new(infile), StreamResult.new(outfile))
end
end # XsltProcessor
class Saxon < XsltProcessor
TRANSFORMER_FACTORY_IMPL = "net.sf.saxon.TransformerFactoryImpl"
def initialize
System.setProperty("javax.xml.transform.TransformerFactory", TRANSFORMER_FACTORY_IMPL)
@tf = TransformerFactory.newInstance
end
end
class Xalan < XsltProcessor
TRANSFORMER_FACTORY_IMPL = "org.apache.xalan.processor.TransformerFactoryImpl"
def initialize
System.setProperty("javax.xml.transform.TransformerFactory", TRANSFORMER_FACTORY_IMPL)
@tf = TransformerFactory.newInstance
end
end
end
# if you wanted to run this from the command line, do something like
# $ jruby lib/jxslt.rb a.xsl in.xml out.xml
xalan = JXslt::Xalan.new
xalan.transform(*ARGV)
#saxon = JXslt::Saxon.new
#saxon.transform(*ARGV)
Big props to Charles for helping me get going and writing the first version of the above.
darcs get http://kfahlgren.com/code/lib/jxslt/ or jxslt.rb
[Read this for an introduction to what I'm talking about].
Now that we’ve got our FrameMaker documents in XML, how can we exploit their new format? One of the first things I did was to create new ways of reading (eventually changing) the simple data stored within them. This isn’t all that earth-shattering, but when you consider how difficult it is to find and change some values in the FrameMaker UI this is a big win. Where to start? Bookfiles.
To be able to apply stylesheets or data-collection tools to books (rather than individual files), I need to be able to collect a books components. So, convert your bookfile to MX (yeah, it works on bookfiles as well as chapter files), and search through it for one of the filenames you know is a part of the book (you’ll probably want to pretty-print the XML first). I get something like this:
<BookComponent>
<FileName>`<c\>ch01'</FileName>
<Unique>27107</Unique>
<StartPageSide>StartRightSide</StartPageSide>
<PageNumbering>Restart</PageNumbering>
<PgfNumbering>Continue</PgfNumbering>
<PageNumPrefix>`'</PageNumPrefix>
<PageNumSuffix>`'</PageNumSuffix>
<DefaultPrint>Yes</DefaultPrint>
<DefaultApply>Yes</DefaultApply>
</BookComponent>
MX in this case has a pretty comprehensible structure, so we’ll need to grab a BookComponent/FileName, do a little text processing to remove the funky characters, and potentially append our MX file extension (I chose “.mx”). Here’s a very simple stylesheet to do just that:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/">
<xsl:element name="components">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="//BookComponent/FileName">
<xsl:param name="extension" select="'.mx'"/>
<xsl:variable name="str-after">
<xsl:value-of select="substring-after(., '>')"/>
</xsl:variable>
<xsl:element name="component">
<xsl:value-of select="substring($str-after,
1,
string-length($str-after) - 1)"/>
<xsl:value-of select="$extension"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When you run that on a MX bookfile, you should see an output like this (note that the file extension is customizable above):
<?xml version="1.0"?> <components> <component>svcTOC.fm.mx</component> <component>foreword.mx</component> <component>ch00.mx</component> <component>ch01.mx</component> <component>ch02.mx</component> <component>ch03.mx</component> <component>ch04.mx</component> <component>ch05.mx</component> <component>ch06.mx</component> <component>ch07.mx</component> <component>ch08.mx</component> <component>ch09.mx</component> <component>appa.mx</component> <component>appb.mx</component> <component>appc.mx</component> <component>appd.mx</component> <component>appe.mx</component> <component>svcIX.fm.mx</component> <component>svcAPL.fm.mx</component> <component>svcLOR.fm.mx</component> </components>
That’ll give us a nice structure to direct other processes to the individual component files.
The code is also available here or darcs get http://kfahlgren.com/code/mx/.
I gave a short talk at the first North Bay Ruby Users Group last Thursday (Feb 15, 2007) about my recent work implementing an Atom Publishing Protocol library in Ruby. Here’s the presentation:
[Read this for an introduction to what I'm talking about].
The first step of doing anything useful with MX is the ability to get back out into MIF. Thankfully, this is an entirely trivial job in XSLT.
[This code thanks to my boss, Andrew Savikas.]
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- author: Andrew Savikas, O'Reilly Media -->
<xsl:output method="text" encoding="ascii"/>
<xsl:strip-space elements="_facet"/>
<xsl:template match="/|MIF_ROOT">
<xsl:apply-templates/>
</xsl:template>
<!-- This template needs to remain flush left for correct output -->
<xsl:template match="_facet">
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*">
<xsl:text><</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text> </xsl:text>
<xsl:apply-templates/>
<xsl:text>></xsl:text>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
The code is also available here or darcs get http://kfahlgren.com/code/mx/.
My O’Reilly colleague Andy Bruno has just written a pair of posts on converting FrameMaker’s MIF (link may be old/die) format into XML (henceforth ‘MX’). I’ll be writing a few posts outlining the ways in which we’ve leveraged MX at O’Reilly.
[Update: Series continues here with getting back into MIF, and reading bookfiles.]
I’m pleased to announce a new Ruby Users Group for folks north of San Francisco (or who like to go to Sebastopol, CA): the North Bay Ruby Users Group with a first meeting on February 15th, 2007 at 7:30pm 7:00pm. O’Reilly has graciously offered us their meeting space, so we’ll be holding the meetings at the office in Sebastopol, CA (directions). The initial schedule for meetings is the third Thursday of each month, but we can have a discussion about whether other times work better at the first meeting. If you’re interested in learning more, please sign up for the mailing list.
[Full disclosure: Both Rob Orsini (the author of the recently released Rails Cookbook) and I are both employees of O'Reilly Media, so I can't pretend that holding the meetings here was a total accident.]
The agenda, as well as everything else, for the first meeting is still in flux, but Rob and I are planning to do a presentation on how O’Reilly uses Ruby internally, from small internal Rails sites to Atom Publishing Protocol libraries to XML processing. We can spend the rest of the time getting to know each other, eating pizza, and planning upcoming meetings. If you’d like to make a presentation at a future meeting or know someone who should, please join the mailing list and make suggestions (even if you can’t make it every month).
Looking forward to seeing you there!
PS: Yeah, the website is pretty crappy right now, we’ll be making it more useful in the coming days, adding a wiki and all that. For now, just be happy it isn’t this.
Especially a picture of SVN commit messages:

PS: That’s an RSS feed via Trac, a decent tool if you can manage to set it up…
So, I’ve been hearing a lot of buzz lately in the Ruby community about BDD and rspec. Like this person on ruby-talk, I didn’t really understand what all the fuss was about. However Dave Astels’ great Google Tech Talk video (worth watching in full rather than just a part, thanks James Britt) has at least inspired me to download rspec and start playing around.
I’m pleased to see that rspec has support for both rcov and heckle built-in, as I’ve been enjoying both recently.