odt-serenity 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +23 -0
- data/README.md +74 -0
- data/Rakefile +6 -0
- data/lib/odt-serenity.rb +1 -0
- data/lib/serenity/debug.rb +19 -0
- data/lib/serenity/escape_xml.rb +18 -0
- data/lib/serenity/generator.rb +22 -0
- data/lib/serenity/helper.rb +7 -0
- data/lib/serenity/helpers/tables_helper.rb +13 -0
- data/lib/serenity/images_processor.rb +32 -0
- data/lib/serenity/line.rb +81 -0
- data/lib/serenity/node_type.rb +7 -0
- data/lib/serenity/odteruby.rb +112 -0
- data/lib/serenity/ruby_lexer.rb +1 -0
- data/lib/serenity/syntactic_sugar.rb +24 -0
- data/lib/serenity/syntactic_sugars/application_sugar.rb +7 -0
- data/lib/serenity/syntactic_sugars/row_sugar.rb +119 -0
- data/lib/serenity/template.rb +38 -0
- data/lib/serenity/version.rb +3 -0
- data/lib/serenity/xml_reader.rb +31 -0
- data/lib/serenity-odt.rb +1 -0
- data/lib/serenity.rb +15 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78d869d91c4ce61e0894727676277786bac24d90a55faaba90d8124f7b009af6
|
4
|
+
data.tar.gz: 9fb183858137444a556fefb79a7caad6feb2375e997b45f5405e2640c0dbdc7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c73043b050e81872efa7d3439082dffde2c77467112312083807639d5b7ff3097e001c4311a493bad5b272ca2c37d1000f03dabb16e9c36d7e4091a905f4777
|
7
|
+
data.tar.gz: e6021fc220675d29be8df904ce7e8a2e5cf34cc9a22cbe5ea6a042faf3369da0182e0856007e1a394b604be5d111986c00a2be619eb2b0fdeb2a3d5ea0274936
|
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,74 @@
|
|
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
|
+
Important Changes
|
4
|
+
=================
|
5
|
+
|
6
|
+
As of version 0.2.0 serenity is using instance variables in the templates. In previous versions, your instance variables would be converted to local before passing to the template, so while in the code you had `@title = 'Serenity'`, you had to put `{%= title }` in the template to get serenity to work. This has now changed and you need to use instance variables in the templates: `{%= @title }`. Honestly, I don't know why I did it this way in the first place, considering that people are used to instance variables in templates from rails.
|
7
|
+
|
8
|
+
Usage
|
9
|
+
======
|
10
|
+
|
11
|
+
Serenity is best demonstrated with an example. 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.
|
12
|
+
|
13
|
+
![Serenity template](http://github.com/kremso/serenity/blob/master/showcase/imgs/serenity_template.png?raw=true)
|
14
|
+
|
15
|
+
The image above is a screenshot of showcase.odt from the [showcase](http://github.com/kremso/serenity/blob/master/showcase) directory. It's a regular OpenOffice document with ruby code embedded inside a special markup. That ruby code drives the document creation. You can use conditionals, loops, blocks — in fact, the whole ruby language and you can apply any OpenOffice formatting to the outputted variables or static text.
|
16
|
+
|
17
|
+
The second line in the template is `{%= @title%}` what means: output the value of variable title. It's bold and big, in fact, it's has the 'Heading 1' style applied. That variable will be replaced in the generated document, but it will still be a 'Heading 1'.
|
18
|
+
|
19
|
+
You can now take that template, provide the data and generate the final document:
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'serenity'
|
23
|
+
|
24
|
+
class Showcase
|
25
|
+
include Serenity::Generator
|
26
|
+
|
27
|
+
Person = Struct.new(:name, :items)
|
28
|
+
Item = Struct.new(:name, :usage)
|
29
|
+
|
30
|
+
def generate_showcase
|
31
|
+
@title = 'Serenity inventory'
|
32
|
+
|
33
|
+
mals_items = [Item.new('Moses Brothers Self-Defense Engine Frontier Model B', 'Lock and load')]
|
34
|
+
mal = Person.new('Malcolm Reynolds', mals_items)
|
35
|
+
|
36
|
+
jaynes_items = [Item.new('Vera', 'Callahan full-bore auto-lock with a customized trigger, double cartridge and thorough gauge'),
|
37
|
+
Item.new('Lux', 'Ratatata'),
|
38
|
+
Item.new('Knife', 'Cut-throat')]
|
39
|
+
jayne = Person.new('Jayne Cobb', jaynes_items)
|
40
|
+
|
41
|
+
@crew = [mal, jayne]
|
42
|
+
|
43
|
+
render_odt 'showcase.odt'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
The key parts are `include Serenity::Generator` and `render_odt`. The data for the template must be provided as instance variables.
|
48
|
+
|
49
|
+
Following picture shows the generated document. It's a screenshot of the showcase_output.odt document from the [showcase](http://github.com/kremso/serenity/blob/master/showcase) directory.
|
50
|
+
|
51
|
+
![Generated document](http://github.com/kremso/serenity/blob/master/showcase/imgs/serenity_output.png?raw=true)
|
52
|
+
|
53
|
+
Installation
|
54
|
+
============
|
55
|
+
|
56
|
+
gem install serenity-odt
|
57
|
+
|
58
|
+
Yeah, serenity is already taken on gemcutter.
|
59
|
+
|
60
|
+
Creating templates
|
61
|
+
===================
|
62
|
+
|
63
|
+
Templates are created directly in OpenOffice. Ruby code is enclosed in special markup:
|
64
|
+
|
65
|
+
+ `{%= %}` is for ruby code which should be output to the final document. `to_s` is applied to anything found inside this markup
|
66
|
+
+ `{% %}` is for everything else — loops, ifs, ends and any other non-outputting code
|
67
|
+
|
68
|
+
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.
|
69
|
+
|
70
|
+
Contact
|
71
|
+
=======
|
72
|
+
|
73
|
+
kramar[dot]tomas[at]gmail.com
|
74
|
+
|
data/Rakefile
ADDED
data/lib/odt-serenity.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './serenity'
|
@@ -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,18 @@
|
|
1
|
+
class String
|
2
|
+
def escape_xml
|
3
|
+
mgsub!([[/&/, '&'], [/</, '<'], [/>/, '>']])
|
4
|
+
end
|
5
|
+
|
6
|
+
def convert_newlines
|
7
|
+
gsub!("\n", '<text:line-break/>')
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def mgsub!(key_value_pairs=[].freeze)
|
12
|
+
regexp_fragments = key_value_pairs.collect { |k,v| k }
|
13
|
+
gsub!(Regexp.union(*regexp_fragments)) do |match|
|
14
|
+
key_value_pairs.detect{|k,v| k =~ match}[1]
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Serenity
|
2
|
+
module Generator
|
3
|
+
def render_odt template_path, output_path = output_name(template_path)
|
4
|
+
template = Template.new template_path, output_path
|
5
|
+
template.process binding
|
6
|
+
end
|
7
|
+
|
8
|
+
def helper
|
9
|
+
@helper ||= Serenity::Helper.new
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def output_name input
|
15
|
+
if input =~ /(.+)\.odt\Z/
|
16
|
+
"#{$1}_output.odt"
|
17
|
+
else
|
18
|
+
"#{input}_output.odt"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Serenity
|
2
|
+
module TablesHelper
|
3
|
+
# TODO: Fix static name of variable #builder
|
4
|
+
def merge_rows_tag(items, col:)
|
5
|
+
items.each_with_index do |item, index|
|
6
|
+
yield(
|
7
|
+
item,
|
8
|
+
OpenStruct.new(subject: item, index: index, count: items.count)
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Serenity
|
2
|
+
class ImagesProcessor
|
3
|
+
include Debug
|
4
|
+
|
5
|
+
IMAGE_DIR_NAME = "Pictures"
|
6
|
+
|
7
|
+
def initialize(xml_content, context)
|
8
|
+
@replacements = []
|
9
|
+
@images = eval('@images', context)
|
10
|
+
@xml_content = xml_content
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_replacements
|
14
|
+
require 'nokogiri'
|
15
|
+
|
16
|
+
if @images && @images.kind_of?(Hash)
|
17
|
+
xml_data = Nokogiri::XML(@xml_content)
|
18
|
+
|
19
|
+
@images.each do |image_name, replacement_path|
|
20
|
+
if node = xml_data.xpath("//draw:frame[@draw:name='#{image_name}']/draw:image").first
|
21
|
+
placeholder_path = node.attribute('href').value
|
22
|
+
odt_image_path = ::File.join(IMAGE_DIR_NAME, ::File.basename(placeholder_path))
|
23
|
+
|
24
|
+
@replacements << [odt_image_path, replacement_path]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@replacements
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
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 clone
|
14
|
+
self.class.new(text)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.text txt
|
18
|
+
TextLine.new txt
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.code txt
|
22
|
+
CodeLine.new txt
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.string txt
|
26
|
+
StringLine.new txt
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.literal txt
|
30
|
+
LiteralLine.new txt
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class TextLine < Line
|
36
|
+
def to_buf
|
37
|
+
" _buf << '" << escape_text(@text) << "';"
|
38
|
+
end
|
39
|
+
|
40
|
+
def escape_text text
|
41
|
+
text.gsub(/['\\]/, '\\\\\&')
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_attribute(key, val)
|
45
|
+
tag = Nokogiri.XML(@text).children[0]
|
46
|
+
tag[key] = val
|
47
|
+
|
48
|
+
updated_text = tag.to_s
|
49
|
+
updated_text.gsub!('/','') unless @text.include?('/')
|
50
|
+
|
51
|
+
@text = updated_text
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CodeLine < Line
|
56
|
+
def to_buf
|
57
|
+
escape_code(@text) << ';'
|
58
|
+
end
|
59
|
+
|
60
|
+
def escape_code code
|
61
|
+
code.mgsub! [[/'/, "'"], [/>/, '>'], [/</, '<'], [/"/, '"'], [/&/, '&']]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class StringLine < CodeLine
|
66
|
+
def to_buf
|
67
|
+
" _buf << (" << escape_code(@text) << ").to_s.escape_xml.convert_newlines;"
|
68
|
+
end
|
69
|
+
|
70
|
+
def convert_newlines text
|
71
|
+
text.gsub("First line", '<text:line-break>')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class LiteralLine < CodeLine
|
76
|
+
def to_buf
|
77
|
+
" _buf << (" << escape_code(@text) << ").to_s;"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Serenity
|
2
|
+
class OdtEruby
|
3
|
+
include Debug
|
4
|
+
|
5
|
+
EMBEDDED_PATTERN = /\{%([=%]+)?(.*?)-?%\}/m
|
6
|
+
|
7
|
+
attr_reader :template
|
8
|
+
|
9
|
+
def initialize template
|
10
|
+
@template = template
|
11
|
+
end
|
12
|
+
|
13
|
+
def src
|
14
|
+
@src ||= begin
|
15
|
+
script = convert_to_ruby_script template
|
16
|
+
|
17
|
+
if debug?
|
18
|
+
File.open(debug_file_path, 'w') { |f| f << script }
|
19
|
+
end
|
20
|
+
|
21
|
+
script
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def evaluate context
|
26
|
+
encoding_src = src.force_encoding Encoding.default_external
|
27
|
+
eval(encoding_src, context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def raw_lines
|
31
|
+
@sugar_lines ||= convert_to_raw_lines template
|
32
|
+
end
|
33
|
+
|
34
|
+
def lines
|
35
|
+
@lines ||= SyntacticSugar.all.translate(raw_lines)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def convert_to_ruby_script(template)
|
41
|
+
src = "_buf = '';"
|
42
|
+
lines.each { |line| src << line.to_buf }
|
43
|
+
src << "\n_buf.to_s\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert_to_raw_lines(template)
|
47
|
+
buffer = []
|
48
|
+
buffer_next = []
|
49
|
+
|
50
|
+
template.each_node do |node, type|
|
51
|
+
if !buffer_next.empty?
|
52
|
+
if is_matching_pair?(buffer.last, node)
|
53
|
+
buffer.pop
|
54
|
+
next
|
55
|
+
elsif is_nonpair_tag? node
|
56
|
+
next
|
57
|
+
else
|
58
|
+
buffer << buffer_next
|
59
|
+
buffer.flatten!
|
60
|
+
buffer_next = []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if type == NodeType::CONTROL
|
65
|
+
buffer_next = process_instruction(node)
|
66
|
+
else
|
67
|
+
buffer << process_instruction(node)
|
68
|
+
buffer.flatten!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
buffer
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_instruction text
|
76
|
+
#text = text.strip
|
77
|
+
pos = 0
|
78
|
+
src = []
|
79
|
+
|
80
|
+
text.scan(EMBEDDED_PATTERN) do |indicator, code|
|
81
|
+
m = Regexp.last_match
|
82
|
+
middle = text[pos...m.begin(0)]
|
83
|
+
pos = m.end(0)
|
84
|
+
src << Line.text(middle) unless middle.empty?
|
85
|
+
|
86
|
+
if !indicator # <% %>
|
87
|
+
src << Line.code(code)
|
88
|
+
elsif indicator == '=' # <%= %>
|
89
|
+
src << Line.string(code)
|
90
|
+
elsif indicator == '%' # <%% %>
|
91
|
+
src << Line.literal(code)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
rest = pos == 0 ? text : text[pos..-1]
|
96
|
+
|
97
|
+
src << Line.text(rest) unless rest.nil? or rest.empty?
|
98
|
+
src
|
99
|
+
end
|
100
|
+
|
101
|
+
def is_nonpair_tag? tag
|
102
|
+
tag =~ /<.+?\/>/
|
103
|
+
end
|
104
|
+
|
105
|
+
def is_matching_pair? open, close
|
106
|
+
open = open.to_s.strip
|
107
|
+
close = close.to_s.strip
|
108
|
+
|
109
|
+
close == "</#{open[1, close.length - 3]}>"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './ruby_lexer/method.rb'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "./syntactic_sugars/application_sugar"
|
2
|
+
require_relative "./syntactic_sugars/row_sugar"
|
3
|
+
|
4
|
+
module Serenity
|
5
|
+
class SyntacticSugar
|
6
|
+
class << self
|
7
|
+
def all
|
8
|
+
new [RowSugar.new]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :sugars
|
13
|
+
|
14
|
+
def initialize(sugars = [])
|
15
|
+
@sugars = sugars
|
16
|
+
end
|
17
|
+
|
18
|
+
def translate(lines)
|
19
|
+
sugars.inject(lines) do |output, sugar|
|
20
|
+
sugar.translate(output)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Serenity
|
2
|
+
class RowSugar < ApplicationSugar
|
3
|
+
def translate(lines)
|
4
|
+
result = []
|
5
|
+
begin_index = nil
|
6
|
+
|
7
|
+
lines.each_with_index do |line, index|
|
8
|
+
if begin_construction? line
|
9
|
+
begin_index = index
|
10
|
+
elsif begin_index && end_construction?(line)
|
11
|
+
result += translate_construction(lines.slice(begin_index, index - begin_index + 1))
|
12
|
+
begin_index = nil
|
13
|
+
elsif begin_index.nil?
|
14
|
+
result << line
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def begin_construction?(line)
|
24
|
+
line.is_a?(Serenity::CodeLine) && line.text.include?('merge_rows_tag')
|
25
|
+
end
|
26
|
+
|
27
|
+
def end_construction?(line)
|
28
|
+
line.is_a?(Serenity::CodeLine) && line.text.include?('end')
|
29
|
+
end
|
30
|
+
|
31
|
+
def begin_cell?(line)
|
32
|
+
line.is_a?(Serenity::TextLine) && line.text.include?('<table:table-cell')
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_cell?(line)
|
36
|
+
line.is_a?(Serenity::TextLine) && line.text.include?('</table:table-cell>')
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_attribute_to_other_row(lines, options)
|
40
|
+
curr_col, curr_index = 0, 0
|
41
|
+
result = []
|
42
|
+
|
43
|
+
while curr_index < lines.length
|
44
|
+
curr_col = curr_col + 1 if begin_cell? lines[curr_index]
|
45
|
+
|
46
|
+
if curr_col == options[:col]
|
47
|
+
result << Line.text("<table:covered-table-cell/>")
|
48
|
+
break
|
49
|
+
else
|
50
|
+
result << lines[curr_index]
|
51
|
+
end
|
52
|
+
|
53
|
+
curr_index = curr_index + 1
|
54
|
+
end
|
55
|
+
|
56
|
+
while curr_index < lines.length
|
57
|
+
if end_cell? lines[curr_index]
|
58
|
+
curr_index = curr_index + 1
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
curr_index = curr_index + 1
|
63
|
+
end
|
64
|
+
|
65
|
+
while curr_index < lines.length
|
66
|
+
result << lines[curr_index]
|
67
|
+
curr_index = curr_index + 1
|
68
|
+
end
|
69
|
+
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_attribute_to_first_row(lines, options)
|
74
|
+
curr_col = 0
|
75
|
+
|
76
|
+
found_index = lines.index do |line|
|
77
|
+
curr_col = curr_col + 1 if begin_cell? line
|
78
|
+
curr_col == options[:col]
|
79
|
+
end
|
80
|
+
|
81
|
+
cloned_lines = lines.map(&:clone)
|
82
|
+
|
83
|
+
if found_index
|
84
|
+
cloned_lines[found_index].set_attribute 'table:number-rows-spanned', '%{rows_count}'
|
85
|
+
cloned_lines[found_index] = Line.literal("'#{cloned_lines[found_index]}' % { rows_count: builder.count }")
|
86
|
+
end
|
87
|
+
|
88
|
+
cloned_lines
|
89
|
+
end
|
90
|
+
|
91
|
+
def translate_construction(lines)
|
92
|
+
first_line, last_line = lines[0], lines[lines.length - 1]
|
93
|
+
|
94
|
+
construction_options = parse_construction_options first_line.text
|
95
|
+
construction_lines = lines.slice(1, lines.length - 2)
|
96
|
+
|
97
|
+
result = [first_line]
|
98
|
+
result << Line.code(" if builder.index == 0 ")
|
99
|
+
result += add_attribute_to_first_row(construction_lines, construction_options)
|
100
|
+
result << Line.code(" else ")
|
101
|
+
result += add_attribute_to_other_row(construction_lines, construction_options)
|
102
|
+
result << Line.code(" end ")
|
103
|
+
result << last_line
|
104
|
+
|
105
|
+
result
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO: May be add gem parser?
|
109
|
+
def parse_construction_options(text)
|
110
|
+
options = {}
|
111
|
+
|
112
|
+
if result = text.match(/col:\s(\d){1,}/)
|
113
|
+
options[:col] = result[1].to_i
|
114
|
+
end
|
115
|
+
|
116
|
+
options
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require '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
|
+
tmpfiles = []
|
15
|
+
Zip::File.open(@template) do |zipfile|
|
16
|
+
%w(content.xml styles.xml).each do |xml_file|
|
17
|
+
content = zipfile.read(xml_file)
|
18
|
+
|
19
|
+
# Images replacement
|
20
|
+
images_replacements = ImagesProcessor.new(content, context).generate_replacements
|
21
|
+
images_replacements.each do |r|
|
22
|
+
zipfile.replace(r.first, r.last)
|
23
|
+
end
|
24
|
+
|
25
|
+
odteruby = OdtEruby.new(XmlReader.new(content))
|
26
|
+
out = odteruby.evaluate(context)
|
27
|
+
out.force_encoding Encoding.default_external
|
28
|
+
|
29
|
+
tmpfiles << (file = Tempfile.new("serenity"))
|
30
|
+
file << out
|
31
|
+
file.close
|
32
|
+
|
33
|
+
zipfile.replace(xml_file, file.path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Serenity
|
2
|
+
class XmlReader
|
3
|
+
|
4
|
+
def initialize src
|
5
|
+
@src = src.force_encoding("UTF-8")
|
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
|
14
|
+
text = @src[last_match_pos...m.begin(0)]
|
15
|
+
yield text, 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
|
data/lib/serenity-odt.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './serenity'
|
data/lib/serenity.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
require 'serenity/version'
|
5
|
+
require 'serenity/line'
|
6
|
+
require 'serenity/debug'
|
7
|
+
require 'serenity/node_type'
|
8
|
+
require 'serenity/escape_xml'
|
9
|
+
require 'serenity/odteruby'
|
10
|
+
require 'serenity/images_processor'
|
11
|
+
require 'serenity/template'
|
12
|
+
require 'serenity/xml_reader'
|
13
|
+
require 'serenity/helper'
|
14
|
+
require 'serenity/syntactic_sugar'
|
15
|
+
require 'serenity/generator'
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: odt-serenity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- kremso
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-03-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubyzip
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.2.9
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.2.9
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Embedded ruby for OpenOffice/LibreOffice Text Document (.odt) files.
|
70
|
+
You provide an .odt template with ruby code in a special markup and the data, and
|
71
|
+
Serenity generates the document. Very similar to .erb files.
|
72
|
+
email: ''
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- lib/odt-serenity.rb
|
81
|
+
- lib/serenity-odt.rb
|
82
|
+
- lib/serenity.rb
|
83
|
+
- lib/serenity/debug.rb
|
84
|
+
- lib/serenity/escape_xml.rb
|
85
|
+
- lib/serenity/generator.rb
|
86
|
+
- lib/serenity/helper.rb
|
87
|
+
- lib/serenity/helpers/tables_helper.rb
|
88
|
+
- lib/serenity/images_processor.rb
|
89
|
+
- lib/serenity/line.rb
|
90
|
+
- lib/serenity/node_type.rb
|
91
|
+
- lib/serenity/odteruby.rb
|
92
|
+
- lib/serenity/ruby_lexer.rb
|
93
|
+
- lib/serenity/syntactic_sugar.rb
|
94
|
+
- lib/serenity/syntactic_sugars/application_sugar.rb
|
95
|
+
- lib/serenity/syntactic_sugars/row_sugar.rb
|
96
|
+
- lib/serenity/template.rb
|
97
|
+
- lib/serenity/version.rb
|
98
|
+
- lib/serenity/xml_reader.rb
|
99
|
+
homepage: https://github.com/mr-dxdy/serenity
|
100
|
+
licenses: []
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubygems_version: 3.1.6
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: Fork of project serenity-odt. Parse ODT file and substitutes placeholders
|
121
|
+
like ERb.
|
122
|
+
test_files: []
|