calco 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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +360 -0
- data/Rakefile +1 -0
- data/calco.gemspec +23 -0
- data/examples/ages.ods +0 -0
- data/examples/compute_cells.rb +61 -0
- data/examples/data.csv +8 -0
- data/examples/example.rb +97 -0
- data/examples/multiplication_tables.ods +0 -0
- data/examples/multiplication_tables.rb +73 -0
- data/examples/register_function.rb +44 -0
- data/examples/using_date_functions.rb +42 -0
- data/examples/write_csv.rb +68 -0
- data/examples/write_ods.rb +69 -0
- data/lib/calco.rb +17 -0
- data/lib/calco/core_ext/fixnum.rb +22 -0
- data/lib/calco/core_ext/float.rb +22 -0
- data/lib/calco/core_ext/range.rb +15 -0
- data/lib/calco/core_ext/string.rb +20 -0
- data/lib/calco/date_functions.rb +13 -0
- data/lib/calco/definition_dsl.rb +127 -0
- data/lib/calco/elements/aggregator.rb +17 -0
- data/lib/calco/elements/builtin_function.rb +84 -0
- data/lib/calco/elements/constant.rb +31 -0
- data/lib/calco/elements/current.rb +19 -0
- data/lib/calco/elements/element.rb +31 -0
- data/lib/calco/elements/empty.rb +9 -0
- data/lib/calco/elements/formula.rb +42 -0
- data/lib/calco/elements/if.rb +26 -0
- data/lib/calco/elements/operation.rb +34 -0
- data/lib/calco/elements/operator.rb +17 -0
- data/lib/calco/elements/or.rb +26 -0
- data/lib/calco/elements/value_extractor.rb +42 -0
- data/lib/calco/elements/variable.rb +35 -0
- data/lib/calco/engines/calculator_builtin_functions.rb +32 -0
- data/lib/calco/engines/csv_engine.rb +80 -0
- data/lib/calco/engines/default_engine.rb +140 -0
- data/lib/calco/engines/office_engine.rb +263 -0
- data/lib/calco/engines/simple_calculator_engine.rb +151 -0
- data/lib/calco/math_functions.rb +9 -0
- data/lib/calco/sheet.rb +363 -0
- data/lib/calco/spreadsheet.rb +172 -0
- data/lib/calco/string_functions.rb +9 -0
- data/lib/calco/style.rb +15 -0
- data/lib/calco/time_functions.rb +12 -0
- data/lib/calco/version.rb +3 -0
- data/spec/absolute_references_spec.rb +86 -0
- data/spec/builtin_functions_spec.rb +161 -0
- data/spec/calculator_engine_spec.rb +251 -0
- data/spec/conditions_spec.rb +118 -0
- data/spec/content_change_spec.rb +190 -0
- data/spec/csv_engine_spec.rb +324 -0
- data/spec/default_engine_spec.rb +135 -0
- data/spec/definitions_spec.rb +65 -0
- data/spec/errors_spec.rb +189 -0
- data/spec/functions_spec.rb +251 -0
- data/spec/header_row_spec.rb +63 -0
- data/spec/range_spec.rb +189 -0
- data/spec/sheet_selections_spec.rb +49 -0
- data/spec/sheet_spec.rb +229 -0
- data/spec/smart_types_spec.rb +43 -0
- data/spec/spreadsheet_spec.rb +80 -0
- data/spec/styles_spec.rb +29 -0
- data/spec/variables_spec.rb +41 -0
- metadata +158 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
module Calco
|
4
|
+
|
5
|
+
class If < Element
|
6
|
+
|
7
|
+
def initialize condition, _then, _else
|
8
|
+
|
9
|
+
super()
|
10
|
+
|
11
|
+
@condition, @_then, @_else = condition, Constant.wrap(_then), Constant.wrap(_else)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate row
|
16
|
+
|
17
|
+
_then = generate_operand(@_then, row)
|
18
|
+
_else = generate_operand(@_else, row)
|
19
|
+
|
20
|
+
"IF(#{@condition.generate(row)};#{_then};#{_else})"
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
module Calco
|
4
|
+
|
5
|
+
class Operation < Element
|
6
|
+
|
7
|
+
include Operators
|
8
|
+
|
9
|
+
def initialize operator, operand1, operand2
|
10
|
+
@operator, @operand1, @operand2 = operator, Constant.wrap(operand1), Constant.wrap(operand2)
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate row
|
14
|
+
|
15
|
+
return "#{@engine.operator(@operator)}#{generate_operand(@operand1, row)}" unless @operand2
|
16
|
+
|
17
|
+
operand1 = generate_operand(@operand1, row)
|
18
|
+
operand2 = generate_operand(@operand2, row)
|
19
|
+
|
20
|
+
"#{operand1}#{@engine.operator(@operator)}#{operand2}"
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def as_operand row
|
25
|
+
"(#{generate(row)})"
|
26
|
+
end
|
27
|
+
|
28
|
+
def -@
|
29
|
+
Operation.new('-', self, nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
module Calco
|
4
|
+
|
5
|
+
class Or < Element
|
6
|
+
|
7
|
+
def initialize condition1, condition2
|
8
|
+
|
9
|
+
super()
|
10
|
+
|
11
|
+
@condition1, @condition2 = Constant.wrap(condition1), Constant.wrap(condition2)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate row
|
16
|
+
|
17
|
+
condition1 = generate_operand(@condition1, row)
|
18
|
+
condition2 = generate_operand(@condition2, row)
|
19
|
+
|
20
|
+
"OR(#{condition1};#{condition2})"
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
module Calco
|
4
|
+
|
5
|
+
class ValueExtractor < Element
|
6
|
+
|
7
|
+
def initialize variable, reference_type
|
8
|
+
@variable, @reference_type = variable, reference_type
|
9
|
+
@variable.reference_type = reference_type
|
10
|
+
end
|
11
|
+
|
12
|
+
def column= column_name
|
13
|
+
@variable.column = column_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def value_name
|
17
|
+
@variable.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
@variable.value
|
22
|
+
end
|
23
|
+
|
24
|
+
def absolute_row
|
25
|
+
@variable.absolute_row
|
26
|
+
end
|
27
|
+
|
28
|
+
def reference_type
|
29
|
+
@variable.reference_type
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate row
|
33
|
+
|
34
|
+
return nil if @variable.absolute_row and @variable.absolute_row != row
|
35
|
+
|
36
|
+
@engine.value(self)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
require_relative 'aggregator'
|
3
|
+
|
4
|
+
module Calco
|
5
|
+
|
6
|
+
class Variable < Element
|
7
|
+
|
8
|
+
include Operators
|
9
|
+
|
10
|
+
attr_accessor :value, :name
|
11
|
+
|
12
|
+
def initialize name, value
|
13
|
+
|
14
|
+
super()
|
15
|
+
|
16
|
+
@name, @value = name, value
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def [] range
|
21
|
+
|
22
|
+
raise ArgumentError, "Expected Range got #{range.class}" unless range.is_a?(Range)
|
23
|
+
raise ArgumentError, "Invalid start of range (must be > 0, was #{range.first})" unless range.first > 0
|
24
|
+
|
25
|
+
Aggregator.new(self, range)
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate row
|
30
|
+
@engine.column_reference(self, row)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
require 'calco'
|
5
|
+
|
6
|
+
module Calco
|
7
|
+
|
8
|
+
module CalculatorBuiltinFunctions
|
9
|
+
|
10
|
+
def YEAR date
|
11
|
+
date.year
|
12
|
+
end
|
13
|
+
|
14
|
+
def TODAY
|
15
|
+
Date.today
|
16
|
+
end
|
17
|
+
|
18
|
+
def DATEVALUE text
|
19
|
+
Date.parse(text)
|
20
|
+
end
|
21
|
+
|
22
|
+
def TIMEVALUE text
|
23
|
+
Time.parse(text)
|
24
|
+
end
|
25
|
+
|
26
|
+
def LEFT text, n
|
27
|
+
text[0, n]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'calco'
|
3
|
+
|
4
|
+
module Calco
|
5
|
+
|
6
|
+
class CSVEngine < Calco::DefaultEngine
|
7
|
+
|
8
|
+
def initialize col_sep = ',', quote_char = '"'
|
9
|
+
@col_sep, @quote_char = col_sep, quote_char
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty_row
|
13
|
+
@out_stream.write CSV.generate_line([])
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_row sheet, row_id
|
17
|
+
|
18
|
+
row_id += 1 if row_id > 0 and sheet.has_titles?
|
19
|
+
|
20
|
+
cells = sheet.row(row_id)
|
21
|
+
|
22
|
+
@out_stream.write CSV.generate_line(cells, :col_sep => @col_sep, :quote_char => @quote_char)
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_cell row_number, column, cell_style, column_style, column_type
|
27
|
+
|
28
|
+
if column.is_a?(Calco::Formula)
|
29
|
+
|
30
|
+
res = super
|
31
|
+
|
32
|
+
if column_type =~ /^[$]/
|
33
|
+
res = "=DOLLAR(#{res})"
|
34
|
+
elsif column_type == '%'
|
35
|
+
res = "=(#{res})%"
|
36
|
+
else
|
37
|
+
res = "=#{res}"
|
38
|
+
end
|
39
|
+
|
40
|
+
elsif column.respond_to?(:value)
|
41
|
+
|
42
|
+
value = column.value
|
43
|
+
|
44
|
+
style = cell_style ? "#{cell_style.generate(row_number)}" : ''
|
45
|
+
|
46
|
+
if value.is_a?(Time)
|
47
|
+
res = "=TIMEVALUE(\"#{value.strftime("%H:%M:%S")}\")#{style}"
|
48
|
+
elsif value.is_a?(Date)
|
49
|
+
res = "=DATEVALUE(\"#{value.strftime("%Y-%m-%d")}\")#{style}"
|
50
|
+
elsif value.is_a?(Numeric)
|
51
|
+
|
52
|
+
if column_type =~ /^[$]/
|
53
|
+
res = "=DOLLAR(#{value}#{style})"
|
54
|
+
elsif column_type == '%'
|
55
|
+
res = "=(#{value}*100)%"
|
56
|
+
else
|
57
|
+
res = "#{value}#{style}"
|
58
|
+
end
|
59
|
+
|
60
|
+
else
|
61
|
+
res = "#{value}#{style}"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
res
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def style statement, row
|
71
|
+
"+STYLE(#{statement.generate(row)})"
|
72
|
+
end
|
73
|
+
|
74
|
+
def current
|
75
|
+
'CURRENT()'
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'calco'
|
2
|
+
|
3
|
+
module Calco
|
4
|
+
|
5
|
+
class DefaultEngine
|
6
|
+
|
7
|
+
# output can be a String (as a file name) or an output stream
|
8
|
+
# examples:
|
9
|
+
# engine.save doc, "my_doc.txt" do |spreadsheet|
|
10
|
+
# ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# engine.save doc, $stdout do |spreadsheet|
|
14
|
+
# ...
|
15
|
+
# end
|
16
|
+
def save doc, output, &data_iterator
|
17
|
+
|
18
|
+
if output.respond_to?(:write)
|
19
|
+
@out_stream = output
|
20
|
+
else
|
21
|
+
@out_stream = open(output, "w")
|
22
|
+
end
|
23
|
+
|
24
|
+
data_iterator.call(doc)
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty_row
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_row sheet, row_id
|
32
|
+
|
33
|
+
cells = sheet.row(row_id)
|
34
|
+
|
35
|
+
cells.each_index do |i|
|
36
|
+
|
37
|
+
cell = cells[i]
|
38
|
+
|
39
|
+
@out_stream.write "#{COLUMNS[i]}#{row_id}: #{cell}\n"
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
@out_stream.write "\n"
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate row_number, columns, cell_styles, column_styles, column_types
|
48
|
+
|
49
|
+
cells = []
|
50
|
+
|
51
|
+
columns.each_with_index do |column, i|
|
52
|
+
cells << generate_cell(row_number, column, cell_styles[i], column_styles[i], column_types[i])
|
53
|
+
end
|
54
|
+
|
55
|
+
cells
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_cell row_number, column, cell_style, column_style, column_type
|
60
|
+
|
61
|
+
cell = '' unless column
|
62
|
+
cell = column.generate(row_number) if column
|
63
|
+
|
64
|
+
if cell_style
|
65
|
+
cell = cell.to_s + cell_style.generate(row_number)
|
66
|
+
end
|
67
|
+
|
68
|
+
cell
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def reference_types
|
73
|
+
{
|
74
|
+
:current => proc { |element, r, c| current },
|
75
|
+
:normal => proc { |element, r, c| "#{c}#{r}" },
|
76
|
+
:absolute => proc { |element, r, c| "$#{c}$#{element.absolute_row}" },
|
77
|
+
:absolute_row => proc { |element, r, c| "#{c}$#{element.absolute_row}" },
|
78
|
+
:absolute_column => proc { |element, r, c| "$#{c}#{r}" }
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def column_reference element, row
|
83
|
+
reference_types[element.reference_type].call(element, row, element.column)
|
84
|
+
end
|
85
|
+
|
86
|
+
def range_reference element, row, range
|
87
|
+
|
88
|
+
from = reference_types[element.reference_type].call(element, range.first, element.column)
|
89
|
+
|
90
|
+
if range.grouping_range?
|
91
|
+
to = row - 1
|
92
|
+
elsif range.last == -1
|
93
|
+
to = DefaultEngine.row_infinity
|
94
|
+
else
|
95
|
+
to = range.last
|
96
|
+
end
|
97
|
+
|
98
|
+
to = reference_types[element.reference_type].call(element, to, element.column)
|
99
|
+
|
100
|
+
"#{from}:#{to}"
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def value element
|
105
|
+
if ! element.respond_to?(:value)
|
106
|
+
element.inspect
|
107
|
+
elsif element.value.is_a?(Time)
|
108
|
+
res = element.value.strftime("%H:%M:%S")
|
109
|
+
elsif element.value.is_a?(Date)
|
110
|
+
res = element.value.strftime("%Y-%m-%d")
|
111
|
+
else
|
112
|
+
element.value.inspect
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def style statement, row
|
117
|
+
"; apply_style(#{statement.generate(row)})"
|
118
|
+
end
|
119
|
+
|
120
|
+
def operator op
|
121
|
+
op
|
122
|
+
end
|
123
|
+
|
124
|
+
def current
|
125
|
+
'self'
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.row_infinity
|
129
|
+
@@row_infinity
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.row_infinity= value
|
133
|
+
@@row_infinity = value
|
134
|
+
end
|
135
|
+
|
136
|
+
@@row_infinity = 1048576
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require 'zip'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'pathname'
|
6
|
+
require 'tempfile'
|
7
|
+
require 'rexml/document'
|
8
|
+
|
9
|
+
require 'calco'
|
10
|
+
|
11
|
+
module Calco
|
12
|
+
|
13
|
+
class OfficeEngine < DefaultEngine
|
14
|
+
|
15
|
+
def initialize ods_template, first_row_is_header = true
|
16
|
+
@ods_template = ods_template
|
17
|
+
@first_row_is_header = first_row_is_header
|
18
|
+
end
|
19
|
+
|
20
|
+
# output is a String (as a file name)
|
21
|
+
def save doc, to_filename, &data_iterator
|
22
|
+
|
23
|
+
content_xml_file = Tempfile.new('office-gen')
|
24
|
+
result_xml_file = Tempfile.new('office-gen')
|
25
|
+
|
26
|
+
Zip::File.open(@ods_template) do |zipfile|
|
27
|
+
content = zipfile.read("content.xml")
|
28
|
+
open(content_xml_file, "w") {|out| out.write content}
|
29
|
+
end
|
30
|
+
|
31
|
+
write_result_content doc, content_xml_file, result_xml_file, @first_row_is_header, &data_iterator
|
32
|
+
|
33
|
+
FileUtils.cp(@ods_template, to_filename)
|
34
|
+
|
35
|
+
Zip::File.open(to_filename) do |zipfile|
|
36
|
+
|
37
|
+
zipfile.get_output_stream("content.xml") do |os|
|
38
|
+
|
39
|
+
File.open(result_xml_file).each_line do |line|
|
40
|
+
os.puts line
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def empty_row
|
50
|
+
@out_stream.write '<table:table-row/>'
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_row sheet, row_id
|
54
|
+
|
55
|
+
return if row_id == 0 and sheet.has_titles?
|
56
|
+
|
57
|
+
row_id += 1 # office sheet indexes start at 1
|
58
|
+
|
59
|
+
cells = sheet.row(row_id)
|
60
|
+
|
61
|
+
@out_stream.write '<table:table-row>'
|
62
|
+
|
63
|
+
cells.each_index do |i|
|
64
|
+
|
65
|
+
cell = cells[i]
|
66
|
+
|
67
|
+
@out_stream.write cell
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
@out_stream.write '</table:table-row>'
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_cell row_number, column, cell_style, column_style, column_type
|
76
|
+
|
77
|
+
return '<table:table-cell/>' unless column
|
78
|
+
return '<table:table-cell/>' if column.absolute_row and column.absolute_row != row_number
|
79
|
+
|
80
|
+
cell = column.generate(row_number)
|
81
|
+
|
82
|
+
if cell_style
|
83
|
+
cell = cell.to_s + cell_style.generate(row_number)
|
84
|
+
end
|
85
|
+
|
86
|
+
if column_style
|
87
|
+
column_style = %[table:style-name="#{column_style}"]
|
88
|
+
else
|
89
|
+
column_style = ''
|
90
|
+
end
|
91
|
+
|
92
|
+
if column_type
|
93
|
+
|
94
|
+
if column_type == '%'
|
95
|
+
column_type = %[office:value-type="percentage"]
|
96
|
+
elsif column_type =~ /\$([A-Z]{3})/
|
97
|
+
column_type = %[office:value-type="currency" office:currency="#{$1}"]
|
98
|
+
else
|
99
|
+
column_type = %[office:value-type="#{column_type}"]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
if column.is_a?(Formula)
|
105
|
+
|
106
|
+
column_type = 'office:value-type="float"' unless column_type
|
107
|
+
|
108
|
+
%[<table:table-cell #{column_style} #{column_type} table:formula="of:=#{cell}" />]
|
109
|
+
|
110
|
+
elsif column.respond_to?(:value)
|
111
|
+
|
112
|
+
if column.value.nil?
|
113
|
+
|
114
|
+
"<table:table-cell/>"
|
115
|
+
|
116
|
+
elsif column.value.is_a?(Numeric)
|
117
|
+
|
118
|
+
column_type = 'office:value-type="float"' unless column_type
|
119
|
+
|
120
|
+
%[<table:table-cell #{column_style} #{column_type} office:value="#{cell}"/>]
|
121
|
+
|
122
|
+
elsif column.value.is_a?(Date)
|
123
|
+
|
124
|
+
column_type = 'office:value-type="date"' unless column_type
|
125
|
+
|
126
|
+
%[<table:table-cell #{column_style} #{column_type} office:date-value="#{cell.to_s}"/>]
|
127
|
+
|
128
|
+
else
|
129
|
+
|
130
|
+
column_type = 'office:value-type="string"' unless column_type
|
131
|
+
|
132
|
+
%[
|
133
|
+
<table:table-cell #{column_style} #{column_type}>
|
134
|
+
<text:p><![CDATA[#{cell}]]></text:p>
|
135
|
+
</table:table-cell>
|
136
|
+
]
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def column_reference element, row
|
145
|
+
element.is_a?(Current) ? "#{super}" : "[.#{super}]"
|
146
|
+
end
|
147
|
+
|
148
|
+
def value element
|
149
|
+
|
150
|
+
if ! element.respond_to?(:value)
|
151
|
+
s = element.to_s
|
152
|
+
else
|
153
|
+
s = element.value.to_s
|
154
|
+
end
|
155
|
+
|
156
|
+
s.gsub!('"', '"') if s =~ /"/
|
157
|
+
s
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
def style statement, row
|
162
|
+
"+ORG.OPENOFFICE.STYLE(#{statement.generate(row)})"
|
163
|
+
end
|
164
|
+
|
165
|
+
def operator op
|
166
|
+
|
167
|
+
if op == '!='
|
168
|
+
'<>'
|
169
|
+
elsif op == '<'
|
170
|
+
'<'
|
171
|
+
elsif op == '>'
|
172
|
+
'>'
|
173
|
+
else
|
174
|
+
op
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
def current
|
180
|
+
'ORG.OPENOFFICE.CURRENT()'
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
# returns the parent table and removes template/example rows, also returns
|
186
|
+
# the first template/example row (to find cell styles for instance)
|
187
|
+
def retrieve_template_row doc, first_row_is_header
|
188
|
+
|
189
|
+
root = doc.root
|
190
|
+
|
191
|
+
count = 0
|
192
|
+
template_row = nil
|
193
|
+
|
194
|
+
table = root.elements['//table:table']
|
195
|
+
table.each_element('table:table-row') do |row|
|
196
|
+
|
197
|
+
if first_row_is_header && count == 0
|
198
|
+
# keep the header row
|
199
|
+
else
|
200
|
+
|
201
|
+
table.delete_element(row)
|
202
|
+
|
203
|
+
template_row = row unless template_row
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
count += 1
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
raise "Cannot find template row in #{@ods_template}" unless template_row
|
212
|
+
|
213
|
+
return table, template_row
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
def create_temporary xml, to_filename
|
218
|
+
|
219
|
+
to = Pathname.new(to_filename)
|
220
|
+
|
221
|
+
temp_file = Tempfile.new('office-gen', to.dirname.to_s)
|
222
|
+
|
223
|
+
File.open(temp_file, 'w') { |stream| stream.puts xml }
|
224
|
+
|
225
|
+
temp_file
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
def write_result_content doc, content_xml_file, result_xml_file, first_row_is_header, &data_iterator
|
230
|
+
|
231
|
+
file = File.new(content_xml_file)
|
232
|
+
|
233
|
+
xml = REXML::Document.new(file)
|
234
|
+
|
235
|
+
table, template_row = retrieve_template_row(xml, first_row_is_header)
|
236
|
+
|
237
|
+
table.add_text "%%%Insert data here%%%\n"
|
238
|
+
|
239
|
+
temp_file = create_temporary(xml, result_xml_file)
|
240
|
+
|
241
|
+
File.open(result_xml_file, 'w') do |stream|
|
242
|
+
|
243
|
+
@out_stream = stream
|
244
|
+
|
245
|
+
File.open(temp_file, 'r').each do |line|
|
246
|
+
|
247
|
+
if line =~ /(.*)%%%Insert data here%%%(.*)/
|
248
|
+
@out_stream.write $1
|
249
|
+
data_iterator.call(doc)
|
250
|
+
@out_stream.write $2
|
251
|
+
else
|
252
|
+
@out_stream.write line
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|