clamsy
Ruby wrapper for generating a single pdf for multiple contexts from an odt template.
1. Basics
A template odt is just an ordinary odt (en.wikipedia.org/wiki/OpenDocument). In order for it to function as a template, we just need to decorate it (just use openoffice, pls don’t buy ms office for that) with placeholders & embedded ruby code.
Under the hood, clamsy is using a slightly hacked version of the awesome tenjin template engine (www.kuwata-lab.com/tenjin) to do the rendering. The following is all u need to know to write odt templates:
-
‘… ?’ represents embedded Ruby statement (this differs from vanilla tenjin)
-
‘${ … }’ represents embedded Ruby expression which is to be escaped (eg. ‘& < > “’ are escaped to ‘& < > "’)
-
‘#{ … }’ represents embedded Ruby expression (u should really avoid using this)
2. Examples
2.1. Text Replacement Example
Template Odt:
--------------------------------------------
${@company_full_name}
${@company_address_line_1}
${@company_address_line_2}
S/N Item Description Amount (SGD)
{? @items.each_with_index do |item, i| ?}
${i+1} ${item.description} ${amount}
{? end ?}
Total ${total}
----------------------------------- Page 1 -
Code:
class Item
attr_reader :description, :amount
def initialize(description, amount)
@description, @amount = description, amount
end
end
items = [
Item.new('Shells (x2 Bags)', 10),
Item.new('Clams (x2 Bags)', 20)
]
context = {
:company_full_name => 'Monster Inc',
:company_address_line_1 => 'Planet Mars, Street xyz,',
:company_address_line_2 => 'Postal Code 009900',
:items => items,
:total => items.inject(0) {|sum, item| sum + item.amount }
}
Clamsy.process(
context,
path_to_template_odt, # eg. '/tmp/invoice.odt'
path_to_final_pdf, # eg. '/tmp/invoice.pdf'
)
Generated Pdf:
--------------------------------------------
Monster Inc
Planet Mars, Street xyz,
Postal Code 009900
S/N Item Description Amount (SGD)
1 Shells (x2 Bags) 10
2 Clams (x2 Bags) 20
Total 30
----------------------------------- Page 1 -
2.2: Multiple Contexts Example
Taking the above example one step further, if we have multiple contexts:
items << Item.new('Shrimps (x2 Bags)', 15)
items << Item.new('Prawns (x2 Bags)', 25)
contexts = [{
:company_full_name => 'Monster Inc',
:company_address_line_1 => 'Planet Mars, Street xyz,',
:company_address_line_2 => 'Postal Code 009900',
:items => items[0..1],
:total => items[0..1].inject(0) {|sum, item| sum + item.amount }
}, {
:company_full_name => 'Fisherman Inc',
:company_address_line_1 => 'Planet Jupiter, Street xyz,',
:company_address_line_2 => 'Postal Code 008800',
:items => items[2..3],
:total => items[2..3].inject(0) {|sum, item| sum + item.amount }
}]
Clamsy.process(
contexts,
path_to_template_odt, # eg. '/tmp/invoice.odt'
path_to_final_pdf # eg. '/tmp/invoice.pdf'
)
Generated Pdf:
--------------------------------------------
Monster Inc
Planet Mars, Street xyz,
Postal Code 009900
S/N Item Description Amount (SGD)
1 Shells (x2 Bags) 10
2 Clams (x2 Bags) 20
Total 30
----------------------------------- Page 1 -
--------------------------------------------
Fisherman Inc
Planet Jupiter, Street xyz,
Postal Code 008800
S/N Item Description Amount (SGD)
1 Shrimps (x2 Bags) 15
2 Prawns (x2 Bags) 25
Total 40
----------------------------------- Page 1 -
As you can see, (whether you like it or not) the pages are merely concatenated together to form a single pdf !!
2.3: Image Replacement Example
It is possible to replace images in an odt file, a useful feature if u need to insert digital signature into the final pdf. To acheive this, u have to:
-
insert a placeholder image into the odt file,
-
resize & move it to your heart content
-
right click on the picture, under [Picture …]/[Options], assign a unique value for [Name]
Template Odt:
--------------------------------------------
~~~~~~~~~~~~~~~~~~
Hey | nice_guy_image |
~~~~~~~~~~~~~~~~~~
This is a gentle reminder that i still
owe you some money ...
~~~~~~~~~~~~~~~~~~~~~~
| my_signature_image |
~~~~~~~~~~~~~~~~~~~~~~
----------------------------------- Page 1 -
Assuming ‘nice_guy_image’ & ‘my_signature_image’ as names of the images, the following code does the appropriate replacing of images:
Clamsy.process(
context = {:_pictures => {
:nice_guy_image => path_to_avatar_image, # eg. '/tmp/handsome.png'
:my_signature_image => path_to_signature_image # eg. '/tmp/signature.png'
}},
path_to_template_odt # eg. '/tmp/confession.odt'
path_to_final_pdf # eg. '/tmp/confession.pdf'
)
3. Configuration
(see wiki.github.com/ngty/clamsy/configuration)
4. Pre-requisites
-
Make sure openoffice, java & ghostscript are installed
TODO
-
address security risk of user running destructive commands within embedded ruby in odt template
-
support for other platforms (eg. windows & jruby)
-
turn off non-santized ruby expression by default (since the generated doc easily breaks when underlying xml is invalid)
-
automate cups-pdf printer setup
Note on Patches/Pull Requests
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Contacts
Written 2010 by:
-
NgTzeYang, contact github.com/ngty
-
JasonOng, contact github.com/jasonong
-
ZhenyiTan, contact github.com/zhenyi
Released under the MIT license