serenity-odt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +23 -0
- data/README.md +60 -0
- data/Rakefile +36 -0
- data/fixtures/advanced.odt +0 -0
- data/fixtures/loop.odt +0 -0
- data/fixtures/loop_table.odt +0 -0
- data/fixtures/table_rows.odt +0 -0
- data/fixtures/variables.odt +0 -0
- data/lib/serenity.rb +8 -0
- data/lib/serenity/debug.rb +19 -0
- data/lib/serenity/generator.rb +31 -0
- data/lib/serenity/line.rb +56 -0
- data/lib/serenity/node_type.rb +7 -0
- data/lib/serenity/odteruby.rb +88 -0
- data/lib/serenity/template.rb +27 -0
- data/lib/serenity/xml_reader.rb +31 -0
- data/serenity-odt.gemspec +36 -0
- data/spec/generator_spec.rb +21 -0
- data/spec/odteruby_spec.rb +102 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/matchers/be_a_document.rb +16 -0
- data/spec/template_spec.rb +48 -0
- data/spec/xml_reader_spec.rb +41 -0
- metadata +106 -0
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2010 Tomas Kramar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Serenity is an embedded ruby for OpenOffice documents (.odt files). You provide an .odt template with ruby code inside a special markup and the data and Serenity generates the document. If you know erb all of this should sound familiar.
|
2
|
+
|
3
|
+
Serenity is best demonstrated with a picture. The first picture shows the template with the ruby code, next image shows the generated document. The template, output and the sample script can be found in the showcase directory.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
Installation
|
10
|
+
============
|
11
|
+
|
12
|
+
gem install serenity-odt
|
13
|
+
|
14
|
+
Yeah, serenity is already taken on gemcutter.
|
15
|
+
|
16
|
+
Creating templates
|
17
|
+
===================
|
18
|
+
|
19
|
+
Templates are created directly in OpenOffice. Ruby code is enclosed in special markup:
|
20
|
+
* `{%= %}` is for ruby code which should be output to the final document. `to_s` is applied to anything found inside this markup
|
21
|
+
* `{% %}` is for everything else — loops, ifs, ends and any other non-outputting code
|
22
|
+
|
23
|
+
Any special formatting should by applied directly on the markup. E.g. if you need to ouput the value of variable title in bold font, write `{%= title %}`, select in in OpenOffice and make it bold. See the showcase.odt for more examples.
|
24
|
+
|
25
|
+
Generating documents
|
26
|
+
====================
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'serenity'
|
30
|
+
|
31
|
+
class Showcase
|
32
|
+
include Serenity::Generator
|
33
|
+
|
34
|
+
Person = Struct.new(:name, :items)
|
35
|
+
Item = Struct.new(:name, :usage)
|
36
|
+
|
37
|
+
def generate_showcase
|
38
|
+
@title = 'Serenity inventory'
|
39
|
+
|
40
|
+
mals_items = [Item.new('Moses Brothers Self-Defense Engine Frontier Model B', 'Lock and load')]
|
41
|
+
mal = Person.new('Malcolm Reynolds', mals_items)
|
42
|
+
|
43
|
+
jaynes_items = [Item.new('Vera', 'Callahan full-bore auto-lock with a customized trigger, double cartridge and thorough gauge'),
|
44
|
+
Item.new('Lux', 'Ratatata'),
|
45
|
+
Item.new('Knife', 'Cut-throat')]
|
46
|
+
jayne = Person.new('Jayne Cobb', jaynes_items)
|
47
|
+
|
48
|
+
@crew = [mal, jayne]
|
49
|
+
|
50
|
+
render_odt 'showcase.odt'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
The key parts are `include Serenity::Generator` and render_odt. The data for the template must be provided as instance variables.
|
55
|
+
|
56
|
+
Contact
|
57
|
+
=======
|
58
|
+
|
59
|
+
kramar[dot]tomas[at]gmail.com — I love the attention
|
60
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
task :default => [:spec]
|
5
|
+
|
6
|
+
Spec::Rake::SpecTask.new("spec")
|
7
|
+
|
8
|
+
spec = Gem::Specification.new do |s|
|
9
|
+
s.name = %q{serenity-odt}
|
10
|
+
s.version = "0.1.0"
|
11
|
+
|
12
|
+
s.authors = ["Tomas Kramar"]
|
13
|
+
s.description = <<-EOF
|
14
|
+
Embedded ruby for OpenOffice Text Document (.odt) files. You provide an .odt template
|
15
|
+
with ruby code in a special markup and the data, and Serenity generates the document.
|
16
|
+
Very similar to .erb files.
|
17
|
+
EOF
|
18
|
+
s.email = %q{kramar.tomas@gmail.com}
|
19
|
+
s.files = Dir.glob('lib/**/*.rb') + %w{README.md Rakefile serenity-odt.gemspec LICENSE}
|
20
|
+
s.has_rdoc = false
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
s.rubygems_version = %q{1.3.5}
|
23
|
+
s.summary = %q{Embedded ruby for OpenOffice Text Document (.odt) files}
|
24
|
+
s.test_files = Dir.glob('spec/**/*.rb') + Dir.glob('fixtures/*.odt')
|
25
|
+
s.add_dependency('rubyzip', '>= 0.9.1')
|
26
|
+
s.add_development_dependency('rspec', '>= 1.2.9')
|
27
|
+
end
|
28
|
+
|
29
|
+
task :gemspec do
|
30
|
+
File.open("serenity-odt.gemspec", "w") { |f| f << spec.to_ruby }
|
31
|
+
end
|
32
|
+
|
33
|
+
task :gem => :gemspec do
|
34
|
+
system "gem build serenity-odt.gemspec"
|
35
|
+
end
|
36
|
+
|
Binary file
|
data/fixtures/loop.odt
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/serenity.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Serenity
|
2
|
+
module Debug
|
3
|
+
def debug?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def debug_file_path
|
8
|
+
File.join(debug_dir, debug_file_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug_file_name
|
12
|
+
"serenity_debug_#{rand(100)}.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug_dir
|
16
|
+
File.join(File.dirname(__FILE__), '..', '..', 'debug')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ruby-debug'
|
2
|
+
|
3
|
+
module Serenity
|
4
|
+
module Generator
|
5
|
+
def render_odt template_path, output_path = output_name(template_path)
|
6
|
+
|
7
|
+
local_variables = {}
|
8
|
+
instance_variables.each { |name| local_variables[name] = instance_variable_get(name) }
|
9
|
+
|
10
|
+
locals = instance_variables.map { |name| "#{ival_name_to_local(name)} = local_variables['#{name}'];" }.join
|
11
|
+
eval locals
|
12
|
+
|
13
|
+
template = Template.new template_path, output_path
|
14
|
+
template.process binding
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def ival_name_to_local ival_name
|
20
|
+
ival_name[1, ival_name.length]
|
21
|
+
end
|
22
|
+
|
23
|
+
def output_name input
|
24
|
+
if input =~ /(.+)\.odt\Z/
|
25
|
+
"#{$1}_output.odt"
|
26
|
+
else
|
27
|
+
"#{input}_output.odt"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Serenity
|
2
|
+
class Line
|
3
|
+
attr_reader :text
|
4
|
+
|
5
|
+
def initialize text
|
6
|
+
@text = text
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
@text
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.text txt
|
14
|
+
TextLine.new txt
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.code txt
|
18
|
+
CodeLine.new txt
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.string txt
|
22
|
+
StringLine.new txt
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class TextLine < Line
|
28
|
+
def to_buf
|
29
|
+
" _buf << '" << escape_text(@text) << "';"
|
30
|
+
end
|
31
|
+
|
32
|
+
def escape_text text
|
33
|
+
text.gsub(/['\\]/, '\\\\\&')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class CodeLine < Line
|
38
|
+
def to_buf
|
39
|
+
escape_code(@text) << ';'
|
40
|
+
end
|
41
|
+
|
42
|
+
def escape_code code
|
43
|
+
code.gsub!(''', "'")
|
44
|
+
code.gsub!('>', ">")
|
45
|
+
code.gsub!('<', "<")
|
46
|
+
code
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class StringLine < CodeLine
|
51
|
+
def to_buf
|
52
|
+
" _buf << (" << escape_code(@text) << ").to_s;"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Serenity
|
2
|
+
class OdtEruby
|
3
|
+
include Debug
|
4
|
+
|
5
|
+
EMBEDDED_PATTERN = /\{%(=+)?(.*?)-?%\}/m
|
6
|
+
|
7
|
+
def initialize template
|
8
|
+
@src = convert template
|
9
|
+
if debug?
|
10
|
+
File.open(debug_file_path, 'w') do |f|
|
11
|
+
f << @src
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate context
|
17
|
+
eval(@src, context)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def convert template
|
23
|
+
src = "_buf = '';"
|
24
|
+
buffer = []
|
25
|
+
buffer_next = []
|
26
|
+
|
27
|
+
template.each_node do |node, type|
|
28
|
+
if !buffer_next.empty?
|
29
|
+
if is_matching_pair?(buffer.last, node)
|
30
|
+
buffer.pop
|
31
|
+
next
|
32
|
+
elsif is_nonpair_tag? node
|
33
|
+
next
|
34
|
+
else
|
35
|
+
buffer << buffer_next
|
36
|
+
buffer.flatten!
|
37
|
+
buffer_next = []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if type == NodeType::CONTROL
|
42
|
+
buffer_next = process_instruction(node)
|
43
|
+
else
|
44
|
+
buffer << process_instruction(node)
|
45
|
+
buffer.flatten!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
buffer.each { |line| src << line.to_buf }
|
50
|
+
src << "\n_buf.to_s\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_instruction text
|
54
|
+
text = text.strip
|
55
|
+
pos = 0
|
56
|
+
src = []
|
57
|
+
|
58
|
+
text.scan(EMBEDDED_PATTERN) do |indicator, code|
|
59
|
+
m = Regexp.last_match
|
60
|
+
middle = text[pos...m.begin(0)]
|
61
|
+
pos = m.end(0)
|
62
|
+
src << Line.text(middle) unless middle.empty?
|
63
|
+
|
64
|
+
if !indicator # <% %>
|
65
|
+
src << Line.code(code)
|
66
|
+
else # <%= %>
|
67
|
+
src << Line.string(code)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
rest = pos == 0 ? text : text[pos..-1]
|
72
|
+
|
73
|
+
src << Line.text(rest) unless rest.nil? or rest.empty?
|
74
|
+
src
|
75
|
+
end
|
76
|
+
|
77
|
+
def is_nonpair_tag? tag
|
78
|
+
tag =~ /<.+?\/>/
|
79
|
+
end
|
80
|
+
|
81
|
+
def is_matching_pair? open, close
|
82
|
+
open = open.to_s.strip
|
83
|
+
close = close.to_s.strip
|
84
|
+
|
85
|
+
close == "</#{open[1, close.length - 3]}>"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'zip/zip'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Serenity
|
5
|
+
class Template
|
6
|
+
attr_accessor :template
|
7
|
+
|
8
|
+
def initialize(template, output)
|
9
|
+
FileUtils.cp(template, output)
|
10
|
+
@template = output
|
11
|
+
end
|
12
|
+
|
13
|
+
def process context
|
14
|
+
Zip::ZipFile.open(@template) do |zipfile|
|
15
|
+
content = zipfile.read('content.xml')
|
16
|
+
odteruby = OdtEruby.new(XmlReader.new(content))
|
17
|
+
out = odteruby.evaluate context
|
18
|
+
|
19
|
+
file = Tempfile.new("serenity")
|
20
|
+
file << out
|
21
|
+
file.close
|
22
|
+
|
23
|
+
zipfile.replace('content.xml', file.path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Serenity
|
2
|
+
class XmlReader
|
3
|
+
|
4
|
+
def initialize src
|
5
|
+
@src = src
|
6
|
+
end
|
7
|
+
|
8
|
+
def each_node
|
9
|
+
last_match_pos = 0
|
10
|
+
|
11
|
+
@src.scan(/<.*?>/) do |node|
|
12
|
+
m = Regexp.last_match
|
13
|
+
if m.begin(0) > last_match_pos + 1
|
14
|
+
text = @src[last_match_pos...m.begin(0)]
|
15
|
+
yield text.strip, node_type(text) if text.gsub(/\s+/, '') != ''
|
16
|
+
end
|
17
|
+
|
18
|
+
last_match_pos = m.end(0)
|
19
|
+
yield node, NodeType::TAG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def node_type text
|
24
|
+
if text =~ /\s*\{%[^=#].+?%\}\s*/
|
25
|
+
NodeType::CONTROL
|
26
|
+
else
|
27
|
+
NodeType::TEMPLATE
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{serenity-odt}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Tomas Kramar"]
|
9
|
+
s.date = %q{2010-02-18}
|
10
|
+
s.description = %q{ Embedded ruby for OpenOffice Text Document (.odt) files. You provide an .odt template
|
11
|
+
with ruby code in a special markup and the data, and Serenity generates the document.
|
12
|
+
Very similar to .erb files.
|
13
|
+
}
|
14
|
+
s.email = %q{kramar.tomas@gmail.com}
|
15
|
+
s.files = ["lib/serenity/debug.rb", "lib/serenity/generator.rb", "lib/serenity/line.rb", "lib/serenity/node_type.rb", "lib/serenity/odteruby.rb", "lib/serenity/template.rb", "lib/serenity/xml_reader.rb", "lib/serenity.rb", "README.md", "Rakefile", "serenity-odt.gemspec", "LICENSE", "spec/generator_spec.rb", "spec/odteruby_spec.rb", "spec/spec_helper.rb", "spec/support/matchers/be_a_document.rb", "spec/template_spec.rb", "spec/xml_reader_spec.rb", "fixtures/advanced.odt", "fixtures/loop.odt", "fixtures/loop_table.odt", "fixtures/table_rows.odt", "fixtures/variables.odt"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubygems_version = %q{1.3.5}
|
18
|
+
s.summary = %q{Embedded ruby for OpenOffice Text Document (.odt) files}
|
19
|
+
s.test_files = ["spec/generator_spec.rb", "spec/odteruby_spec.rb", "spec/spec_helper.rb", "spec/support/matchers/be_a_document.rb", "spec/template_spec.rb", "spec/xml_reader_spec.rb", "fixtures/advanced.odt", "fixtures/loop.odt", "fixtures/loop_table.odt", "fixtures/table_rows.odt", "fixtures/variables.odt"]
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_runtime_dependency(%q<rubyzip>, [">= 0.9.1"])
|
27
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
|
30
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
|
34
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Serenity
|
4
|
+
describe Generator do
|
5
|
+
it 'should make context from instance variables and run the provided template' do
|
6
|
+
class GeneratorClient
|
7
|
+
include Generator
|
8
|
+
|
9
|
+
def generate_odt
|
10
|
+
@crew = ['Mal', 'Inara', 'Wash', 'Zoe']
|
11
|
+
|
12
|
+
render_odt fixture('loop.odt')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
client = GeneratorClient.new
|
17
|
+
lambda { client.generate_odt }.should_not raise_error
|
18
|
+
fixture('loop_output.odt').should be_a_document
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Serenity
|
4
|
+
|
5
|
+
describe OdtEruby do
|
6
|
+
before(:each) do
|
7
|
+
name = 'test_name'
|
8
|
+
type = 'test_type'
|
9
|
+
rows = []
|
10
|
+
rows << Ship.new('test_name_1', 'test_type_1')
|
11
|
+
rows << Ship.new('test_name_2', 'test_type_2')
|
12
|
+
@context = binding
|
13
|
+
end
|
14
|
+
|
15
|
+
def squeeze text
|
16
|
+
text.inject('') { |memo, line| memo += line.strip } unless text.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should replace variables with values from context' do
|
20
|
+
template = <<-EOF
|
21
|
+
<text:p text:style-name="Text_1_body">{%= name %}</text:p>
|
22
|
+
<text:p text:style-name="Text_1_body">{%= type %}</text:p>
|
23
|
+
<text:p text:style-name="Text_1_body"/>
|
24
|
+
EOF
|
25
|
+
|
26
|
+
expected = <<-EOF
|
27
|
+
<text:p text:style-name="Text_1_body">test_name</text:p>
|
28
|
+
<text:p text:style-name="Text_1_body">test_type</text:p>
|
29
|
+
<text:p text:style-name="Text_1_body"/>
|
30
|
+
EOF
|
31
|
+
|
32
|
+
content = OdtEruby.new(XmlReader.new template)
|
33
|
+
result = content.evaluate @context
|
34
|
+
|
35
|
+
squeeze(expected).should == squeeze(result)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should replace multiple variables on one line' do
|
39
|
+
template = '<text:p text:style-name="Text_1_body">{%= type %} and {%= name %}</text:p>'
|
40
|
+
expected = '<text:p text:style-name="Text_1_body">test_type and test_name</text:p>'
|
41
|
+
|
42
|
+
content = OdtEruby.new(XmlReader.new template)
|
43
|
+
result = content.evaluate @context
|
44
|
+
|
45
|
+
squeeze(expected).should == squeeze(result)
|
46
|
+
end
|
47
|
+
=begin
|
48
|
+
should 'replace a LOOP construct with variables' do
|
49
|
+
template = <<-EOF
|
50
|
+
<text:p text:style-name="Text_1_body">{% for row in rows do %}</text:p>
|
51
|
+
<text:p text:style-name="Text_1_body">{%= row.name %}</text:p>
|
52
|
+
<text:p text:style-name="Text_1_body">{%= row.type %}</text:p>
|
53
|
+
<text:p text:style-name="Text_1_body">{% end %}</text:p>
|
54
|
+
EOF
|
55
|
+
|
56
|
+
expected = <<-EOF
|
57
|
+
<text:p text:style-name="Text_1_body">test_name_1</text:p>
|
58
|
+
<text:p text:style-name="Text_1_body">test_type_1</text:p>
|
59
|
+
<text:p text:style-name="Text_1_body">test_name_2</text:p>
|
60
|
+
<text:p text:style-name="Text_1_body">test_type_2</text:p>
|
61
|
+
EOF
|
62
|
+
|
63
|
+
content = OdtEruby.new(XmlReader.new template)
|
64
|
+
result = content.evaluate @context
|
65
|
+
assert_equal expected, result
|
66
|
+
end
|
67
|
+
=end
|
68
|
+
|
69
|
+
it 'should remove empty tags after a control structure processing' do
|
70
|
+
template = <<-EOF
|
71
|
+
<table:table style="Table_1">
|
72
|
+
<table:row style="Table_1_A1">
|
73
|
+
<table:cell style="Table_1_A1_cell">
|
74
|
+
{% for row in rows do %}
|
75
|
+
</table:cell>
|
76
|
+
</table:row>
|
77
|
+
<text:p text:style-name="Text_1_body">{%= row.name %}</text:p>
|
78
|
+
<text:p text:style-name="Text_1_body">{%= row.type %}</text:p>
|
79
|
+
<table:row style="Table_1_A1">
|
80
|
+
<table:cell style="Table_1_A1_cell">
|
81
|
+
{% end %}
|
82
|
+
</table:cell>
|
83
|
+
</table:row>
|
84
|
+
</table:table>
|
85
|
+
EOF
|
86
|
+
|
87
|
+
expected = <<-EOF
|
88
|
+
<table:table style="Table_1">
|
89
|
+
<text:p text:style-name="Text_1_body">test_name_1</text:p>
|
90
|
+
<text:p text:style-name="Text_1_body">test_type_1</text:p>
|
91
|
+
<text:p text:style-name="Text_1_body">test_name_2</text:p>
|
92
|
+
<text:p text:style-name="Text_1_body">test_type_2</text:p>
|
93
|
+
</table:table>
|
94
|
+
EOF
|
95
|
+
|
96
|
+
content = OdtEruby.new(XmlReader.new template)
|
97
|
+
result = content.evaluate @context
|
98
|
+
|
99
|
+
squeeze(expected).should == squeeze(result)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'serenity'
|
5
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
|
6
|
+
|
7
|
+
Ship = Struct.new(:name, :type)
|
8
|
+
Person = Struct.new(:name, :skill)
|
9
|
+
|
10
|
+
def fixture name
|
11
|
+
File.join(File.dirname(__FILE__), '..', 'fixtures', name)
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Serenity
|
2
|
+
Spec::Matchers.define :be_a_document do
|
3
|
+
match do |actual|
|
4
|
+
File.exists? actual
|
5
|
+
end
|
6
|
+
|
7
|
+
failure_message_for_should do |actual|
|
8
|
+
"expected that a file #{actual} would exist"
|
9
|
+
end
|
10
|
+
|
11
|
+
failure_message_for_should_not do |actual|
|
12
|
+
"expected that a file #{actual} would not exist"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Serenity
|
5
|
+
|
6
|
+
describe Template do
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
FileUtils.rm(Dir['*.odt'])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should process a document with simple variable substitution" do
|
13
|
+
name = 'Malcolm Reynolds'
|
14
|
+
title = 'captain'
|
15
|
+
|
16
|
+
template = Template.new(fixture('variables.odt'), 'output_variables.odt')
|
17
|
+
lambda {template.process binding}.should_not raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should unroll a simple for loop" do
|
21
|
+
crew = %w{'River', 'Jayne', 'Wash'}
|
22
|
+
|
23
|
+
template = Template.new(fixture('loop.odt'), 'output_loop.odt')
|
24
|
+
lambda {template.process binding}.should_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should unroll an advanced loop with tables" do
|
28
|
+
ships = [Ship.new('Firefly', 'transport'), Ship.new('Colonial', 'battle')]
|
29
|
+
|
30
|
+
template = Template.new(fixture('loop_table.odt'), 'output_loop_table.odt')
|
31
|
+
lambda {template.process binding}.should_not raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should process an advanced document" do
|
35
|
+
persons = [Person.new('Malcolm', 'captain'), Person.new('River', 'psychic'), Person.new('Jay', 'gunslinger')]
|
36
|
+
|
37
|
+
template = Template.new(fixture('advanced.odt'), 'output_advanced.odt')
|
38
|
+
lambda {template.process binding}.should_not raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should loop and generate table rows" do
|
42
|
+
ships = [Ship.new('Firefly', 'transport'), Ship.new('Colonial', 'battle')]
|
43
|
+
|
44
|
+
template = Template.new(fixture('table_rows.odt'), 'output_table_rows.odt')
|
45
|
+
lambda {template.process binding}.should_not raise_error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Serenity
|
4
|
+
|
5
|
+
Node = Struct.new(:text, :type)
|
6
|
+
|
7
|
+
describe XmlReader do
|
8
|
+
it "should stream the xml, tag by tag" do
|
9
|
+
xml = <<-EOF
|
10
|
+
<?xml version="1.0" encoding="UTF-8"?><office:document-content><office:scripts/>
|
11
|
+
<office:body><text:p style="Standard">This is a sentence</text:p><text:s>{%= yeah %}</text:s></office:body>
|
12
|
+
{% for row in rows %}
|
13
|
+
</office:document-content>
|
14
|
+
EOF
|
15
|
+
|
16
|
+
expected = [Node.new('<?xml version="1.0" encoding="UTF-8"?>', NodeType::TAG),
|
17
|
+
Node.new('<office:document-content>', NodeType::TAG),
|
18
|
+
Node.new('<office:scripts/>', NodeType::TAG),
|
19
|
+
Node.new('<office:body>', NodeType::TAG),
|
20
|
+
Node.new('<text:p style="Standard">', NodeType::TAG),
|
21
|
+
Node.new('This is a sentence', NodeType::TEMPLATE),
|
22
|
+
Node.new('</text:p>', NodeType::TAG),
|
23
|
+
Node.new('<text:s>', NodeType::TAG),
|
24
|
+
Node.new('{%= yeah %}', NodeType::TEMPLATE),
|
25
|
+
Node.new('</text:s>', NodeType::TAG),
|
26
|
+
Node.new('</office:body>', NodeType::TAG),
|
27
|
+
Node.new('{% for row in rows %}', NodeType::CONTROL),
|
28
|
+
Node.new('</office:document-content>', NodeType::TAG)]
|
29
|
+
|
30
|
+
reader = XmlReader.new xml
|
31
|
+
|
32
|
+
idx = 0
|
33
|
+
reader.each_node do |node, type|
|
34
|
+
expected[idx].text.should == node
|
35
|
+
expected[idx].type.should == type
|
36
|
+
idx += 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: serenity-odt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomas Kramar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-18 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyzip
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.9
|
34
|
+
version:
|
35
|
+
description: " Embedded ruby for OpenOffice Text Document (.odt) files. You provide an .odt template\n with ruby code in a special markup and the data, and Serenity generates the document.\n Very similar to .erb files.\n"
|
36
|
+
email: kramar.tomas@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/serenity/debug.rb
|
45
|
+
- lib/serenity/generator.rb
|
46
|
+
- lib/serenity/line.rb
|
47
|
+
- lib/serenity/node_type.rb
|
48
|
+
- lib/serenity/odteruby.rb
|
49
|
+
- lib/serenity/template.rb
|
50
|
+
- lib/serenity/xml_reader.rb
|
51
|
+
- lib/serenity.rb
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- serenity-odt.gemspec
|
55
|
+
- LICENSE
|
56
|
+
- spec/generator_spec.rb
|
57
|
+
- spec/odteruby_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/support/matchers/be_a_document.rb
|
60
|
+
- spec/template_spec.rb
|
61
|
+
- spec/xml_reader_spec.rb
|
62
|
+
- fixtures/advanced.odt
|
63
|
+
- fixtures/loop.odt
|
64
|
+
- fixtures/loop_table.odt
|
65
|
+
- fixtures/table_rows.odt
|
66
|
+
- fixtures/variables.odt
|
67
|
+
has_rdoc: true
|
68
|
+
homepage:
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.3.5
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Embedded ruby for OpenOffice Text Document (.odt) files
|
95
|
+
test_files:
|
96
|
+
- spec/generator_spec.rb
|
97
|
+
- spec/odteruby_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
- spec/support/matchers/be_a_document.rb
|
100
|
+
- spec/template_spec.rb
|
101
|
+
- spec/xml_reader_spec.rb
|
102
|
+
- fixtures/advanced.odt
|
103
|
+
- fixtures/loop.odt
|
104
|
+
- fixtures/loop_table.odt
|
105
|
+
- fixtures/table_rows.odt
|
106
|
+
- fixtures/variables.odt
|