documatic 0.0.2 → 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/README +22 -5
- data/lib/documatic.rb +4 -3
- data/lib/documatic/component.rb +14 -1
- data/lib/documatic/formatter/open_document.rb +13 -11
- data/lib/documatic/open_document_spreadsheet/helper.rb +84 -0
- data/lib/documatic/open_document_spreadsheet/template.rb +258 -0
- data/lib/documatic/open_document_text/helper.rb +12 -1
- data/lib/documatic/open_document_text/partial.rb +1 -1
- data/lib/documatic/open_document_text/template.rb +28 -13
- metadata +14 -11
data/README
CHANGED
@@ -5,6 +5,8 @@ is a template-driven formatter that can be used to produce attractive
|
|
5
5
|
printable documents such as database reports, invoices, letters, faxes
|
6
6
|
and more.
|
7
7
|
|
8
|
+
As of version 0.1.0, Documatic supports OpenDocument text and
|
9
|
+
spreadsheet formats.
|
8
10
|
|
9
11
|
== Installation
|
10
12
|
|
@@ -21,12 +23,27 @@ Documatic is a Ruport extension, so it is loaded like this:
|
|
21
23
|
require 'ruport'
|
22
24
|
require 'ruport/extensions'
|
23
25
|
|
24
|
-
Documatic will then be available as a formatter for your Ruport
|
26
|
+
Documatic will then be available as a formatter for your Ruport
|
27
|
+
tables, groups and groupings. Rendering using an OpenDocument text
|
28
|
+
template is done like this:
|
25
29
|
|
26
|
-
data.to_odt_template(:
|
27
|
-
:
|
30
|
+
data.to_odt_template(:template_file => 'path/filename.odt',
|
31
|
+
:output_file => 'output-path/filename.odt')
|
28
32
|
|
29
|
-
|
33
|
+
Rendering using an OpenDocument spreadsheet is done like this:
|
34
|
+
|
35
|
+
data.to_ods_template(:template_file => 'path/filename.ods',
|
36
|
+
:output_file => 'output-path/filename.ods')
|
37
|
+
|
38
|
+
Documatic creates the output path if required.
|
39
|
+
|
40
|
+
The wiki (see below) has more documentation and examples, plus simple
|
41
|
+
tutorials to illustrate how text and spreadsheet rendering works.
|
42
|
+
|
43
|
+
|
44
|
+
== Version
|
45
|
+
|
46
|
+
0.1.0, released 02-SEP-2007.
|
30
47
|
|
31
48
|
|
32
49
|
== Licence
|
@@ -51,7 +68,7 @@ http://stonecode.svnrepository.com/svn/documatic.
|
|
51
68
|
|
52
69
|
The Rubyforge project page for Documatic is at
|
53
70
|
http://rubyforge.org/projects/documatic. The Documatic rubygem is
|
54
|
-
distributed from
|
71
|
+
distributed from there.
|
55
72
|
|
56
73
|
For any other enquiries please contact Dave Nelson (urbanus at 240gl
|
57
74
|
dot org).
|
data/lib/documatic.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'documatic/open_document_text/helper'
|
2
2
|
require 'documatic/open_document_text/template'
|
3
|
+
require 'documatic/open_document_spreadsheet/template'
|
4
|
+
require 'documatic/open_document_spreadsheet/helper'
|
3
5
|
require 'documatic/component'
|
4
6
|
require 'documatic/open_document_text/partial'
|
5
7
|
|
6
|
-
|
7
8
|
# The module "Documatic" is the namespace for the other modules and
|
8
9
|
# classes in this project. It also contains some convenience methods.
|
9
10
|
module Documatic
|
10
11
|
class << self
|
11
12
|
|
12
13
|
# Short-cut method for including a helper in
|
13
|
-
#
|
14
|
-
def
|
14
|
+
# Documatic::Component (the ERb processor).
|
15
|
+
def add_helper(helper_module)
|
15
16
|
Documatic::Component.class_eval do
|
16
17
|
include helper_module
|
17
18
|
end
|
data/lib/documatic/component.rb
CHANGED
@@ -4,10 +4,13 @@ module Documatic
|
|
4
4
|
class Component
|
5
5
|
include ERB::Util
|
6
6
|
include Documatic::OpenDocumentText::Helper
|
7
|
+
include Documatic::OpenDocumentSpreadsheet::Helper
|
7
8
|
|
8
9
|
attr_accessor :erb
|
10
|
+
attr_accessor :erb_text
|
9
11
|
|
10
12
|
def initialize(erb_text)
|
13
|
+
@erb_text = erb_text
|
11
14
|
@erb = ERB.new(erb_text)
|
12
15
|
end
|
13
16
|
|
@@ -22,7 +25,17 @@ module Documatic
|
|
22
25
|
context = binding
|
23
26
|
end
|
24
27
|
|
25
|
-
|
28
|
+
begin
|
29
|
+
@xml = nil ; @text = self.erb.result(context)
|
30
|
+
rescue
|
31
|
+
lines = self.erb_text.split /\n/
|
32
|
+
counter = 1
|
33
|
+
lines.each do |line|
|
34
|
+
puts "#{counter}:\t#{line}"
|
35
|
+
counter += 1
|
36
|
+
end
|
37
|
+
raise
|
38
|
+
end
|
26
39
|
end
|
27
40
|
|
28
41
|
# Returns a REXML::Document constructed from the text of this
|
@@ -4,24 +4,26 @@ require 'ruport'
|
|
4
4
|
module Documatic::Formatter
|
5
5
|
|
6
6
|
class OpenDocumentText < Ruport::Formatter
|
7
|
-
|
7
|
+
class << self
|
8
|
+
attr_accessor :processor
|
9
|
+
end
|
10
|
+
|
11
|
+
self.processor = Documatic::OpenDocumentText::Template
|
8
12
|
renders :odt_template, :for => [ Ruport::Renderer::Table, Ruport::Renderer::Group,
|
9
13
|
Ruport::Renderer::Grouping ]
|
10
14
|
|
11
15
|
def build
|
12
|
-
|
16
|
+
self.class.processor.process_template(:data => data, :options => options)
|
13
17
|
end
|
14
18
|
alias_method :build_table_body, :build # for Ruport::Renderer::Table
|
15
19
|
alias_method :build_group_body, :build # for Ruport::Renderer::Group
|
16
20
|
alias_method :build_grouping_body, :build # for Ruport::Renderer::Grouping
|
17
|
-
|
18
21
|
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
23
|
+
class OpenDocumentSpreadsheet < OpenDocumentText
|
24
|
+
self.processor = Documatic::OpenDocumentSpreadsheet::Template
|
25
|
+
renders :ods_template, :for => [ Ruport::Renderer::Table, Ruport::Renderer::Group,
|
26
|
+
Ruport::Renderer::Grouping ]
|
27
|
+
end
|
28
|
+
|
29
|
+
end # module Documatic::Formatter
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Documatic
|
6
|
+
module OpenDocumentSpreadsheet
|
7
|
+
module Helper
|
8
|
+
include ERB::Util
|
9
|
+
|
10
|
+
# Map of OOCalc's visible types (selectable in the UI) to what's
|
11
|
+
# stored internally as the office:value-type attribute of a
|
12
|
+
# cell.
|
13
|
+
TYPES = {
|
14
|
+
'Number' => 'float',
|
15
|
+
'Percent' => 'percentage',
|
16
|
+
'Currency' => 'currency',
|
17
|
+
'Date' => 'date',
|
18
|
+
'Time' => 'time',
|
19
|
+
'Scientific' => 'float',
|
20
|
+
'Fraction' => 'string',
|
21
|
+
'Boolean' => 'boolean',
|
22
|
+
'Text' => 'string',
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
# Renders a complete cell element with options to control the
|
27
|
+
# type, style, formula, row and column spans, and other cell
|
28
|
+
# attributes. See the wiki for full details.
|
29
|
+
def cell(value, opts = nil)
|
30
|
+
opts ||= Hash.new
|
31
|
+
opts[:type] ||= (case value.class.to_s
|
32
|
+
when 'Fixnum' then 'Number'
|
33
|
+
when 'Float' then 'Number'
|
34
|
+
when 'DateTime' then 'Date'
|
35
|
+
when 'Date' then 'Date'
|
36
|
+
when 'Time' then 'Time'
|
37
|
+
when 'TrueClass' then 'Boolean'
|
38
|
+
when 'FalseClass' then 'Boolean'
|
39
|
+
else 'Text'
|
40
|
+
end )
|
41
|
+
# Setting the :currency option forces the type to 'Currency'
|
42
|
+
if opts.has_key?(:currency)
|
43
|
+
opts[:type] = 'Currency'
|
44
|
+
end
|
45
|
+
|
46
|
+
# START OUTPUT
|
47
|
+
output = '<table:table-cell'
|
48
|
+
# Add style if specified
|
49
|
+
opts.has_key?(:style) &&
|
50
|
+
output << " table:style-name=\"#{opts[:style]}\""
|
51
|
+
# Add formula if specified
|
52
|
+
opts.has_key?(:formula) &&
|
53
|
+
output << " table:formula=\"#{opts[:formula]}\""
|
54
|
+
# Add the value-type attribute for the type
|
55
|
+
output << " office:value-type=\"#{TYPES[opts[:type]]}\""
|
56
|
+
# Add row and column spans if specified
|
57
|
+
opts.has_key?(:colspan) &&
|
58
|
+
output << " table:number-columns-spanned=\"#{opts[:colspan]}\""
|
59
|
+
opts.has_key?(:rowspan) &&
|
60
|
+
output << " table:number-rows-spanned=\"#{opts[:rowspan]}\""
|
61
|
+
# The rest of the output depends on the type
|
62
|
+
case opts[:type]
|
63
|
+
when 'Number', 'Percent', 'Scientific'
|
64
|
+
output << " office:value=\"#{ERB::Util.h(value)}\">"
|
65
|
+
when 'Currency'
|
66
|
+
output << " office:currency=\"#{ERB::Util.h(opts[:currency])}\""
|
67
|
+
output << " office:value=\"#{ERB::Util.h(value)}\">"
|
68
|
+
when 'Date'
|
69
|
+
output << " office:date-value=\"#{value.strftime("%Y-%m-%dT%H:%M:%S")}\">"
|
70
|
+
when 'Time'
|
71
|
+
output << " office:time-value=\"#{value.strftime("PT%HH%MM%SS")}\">"
|
72
|
+
when 'Boolean'
|
73
|
+
output << " office:boolean-value=\"#{value.to_s}\">"
|
74
|
+
else # text or fraction
|
75
|
+
output << "><text:p>#{ERB::Util.h(value)}</text:p>"
|
76
|
+
end
|
77
|
+
output << "</table:table-cell>"
|
78
|
+
|
79
|
+
return output
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'rexml/text'
|
3
|
+
require 'rexml/attribute'
|
4
|
+
require 'zip/zip'
|
5
|
+
require 'erb'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module Documatic::OpenDocumentSpreadsheet
|
9
|
+
class Template
|
10
|
+
include ERB::Util
|
11
|
+
|
12
|
+
attr_accessor :content
|
13
|
+
attr_accessor :styles
|
14
|
+
attr_accessor :jar
|
15
|
+
# The raw contents of 'content.xml'.
|
16
|
+
attr_accessor :content_raw
|
17
|
+
# Compiled text, to be written to 'content.erb'
|
18
|
+
attr_accessor :content_erb
|
19
|
+
|
20
|
+
# RE_STYLES match positions
|
21
|
+
STYLE_NAME = 1
|
22
|
+
STYLE_TYPE = 2
|
23
|
+
|
24
|
+
# RE_ERB match positions
|
25
|
+
ROW_START = 1
|
26
|
+
TYPE = 2
|
27
|
+
ERB_CODE = 3
|
28
|
+
ROW_END = 4
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
# Includes the number and text helpers from Rails' ActionPack.
|
33
|
+
# Requires that the Rails gems be installed.
|
34
|
+
def include_rails_helpers
|
35
|
+
require 'action_pack'
|
36
|
+
require 'action_controller'
|
37
|
+
require 'action_view'
|
38
|
+
|
39
|
+
require 'action_view/helpers/number_helper'
|
40
|
+
require 'action_view/helpers/text_helper'
|
41
|
+
|
42
|
+
self.class_eval do
|
43
|
+
include ActionView::Helpers::NumberHelper
|
44
|
+
include ActionView::Helpers::TextHelper
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_template(args, &block)
|
50
|
+
if args[:options] && args[:options].template_file && args[:options].output_file
|
51
|
+
output_dir = File.dirname(args[:options].output_file)
|
52
|
+
File.directory?(output_dir) || FileUtils.mkdir_p(output_dir)
|
53
|
+
FileUtils.cp(args[:options].template_file, args[:options].output_file)
|
54
|
+
template = self.new(args[:options].output_file)
|
55
|
+
template.process :data => args[:data], :options => args[:options]
|
56
|
+
template.save
|
57
|
+
if block
|
58
|
+
block.call(template)
|
59
|
+
template.save
|
60
|
+
end
|
61
|
+
template.close
|
62
|
+
else
|
63
|
+
raise ArgumentError, 'Need to specify both :template_file and :output_file in options'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end # class << self
|
68
|
+
|
69
|
+
def initialize(filename)
|
70
|
+
@filename = filename
|
71
|
+
@jar = Zip::ZipFile.open(@filename)
|
72
|
+
return true
|
73
|
+
end
|
74
|
+
|
75
|
+
def process(local_assigns = {})
|
76
|
+
# Compile this template, if not compiled already.
|
77
|
+
self.jar.find_entry('documatic/master') || self.compile
|
78
|
+
# Process the main (body) content.
|
79
|
+
@content = Documatic::Component.new( self.jar.read('documatic/master/content.erb') )
|
80
|
+
@content.process(local_assigns)
|
81
|
+
@content.merge_partial_styles
|
82
|
+
end
|
83
|
+
|
84
|
+
def save
|
85
|
+
# Gather all the styles from the partials, add them to the master's styles.
|
86
|
+
# Put the body into the document.
|
87
|
+
self.jar.get_output_stream('content.xml') do |f|
|
88
|
+
f.write self.content.to_s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def close
|
93
|
+
self.jar.close
|
94
|
+
end
|
95
|
+
|
96
|
+
def compile
|
97
|
+
# Read the raw files
|
98
|
+
@content_raw = regularise_styles( self.jar.read('content.xml') )
|
99
|
+
@content_erb = self.erbify(@content_raw)
|
100
|
+
|
101
|
+
# Create 'documatic/master/' in zip file
|
102
|
+
self.jar.find_entry('documatic/master') || self.jar.mkdir('documatic/master')
|
103
|
+
|
104
|
+
self.jar.get_output_stream('documatic/master/content.erb') do |f|
|
105
|
+
f.write @content_erb
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
# Change OpenDocument line breaks and tabs in the ERb code to regular characters.
|
113
|
+
def unnormalize(code)
|
114
|
+
code = code.gsub(/<text:line-break\/>/, "\n")
|
115
|
+
code = code.gsub(/<text:tab\/>/, "\t")
|
116
|
+
return REXML::Text.unnormalize(code)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Massage OpenDocument XML into ERb. (This is the heart of the compiler.)
|
120
|
+
def erbify(code)
|
121
|
+
# First gather all the ERb-related derived styles
|
122
|
+
remaining = code
|
123
|
+
styles = {'Ruby_20_Code' => 'Code', 'Ruby_20_Value' => 'Value', 'Ruby_20_Literal' => 'Literal'}
|
124
|
+
re_styles = /<style:style style:name="([^"]+)" style:parent-style-name="Ruby_20_(Code|Value|Literal)" style:family="table-cell">/
|
125
|
+
|
126
|
+
while remaining.length > 0
|
127
|
+
md = re_styles.match remaining
|
128
|
+
if md
|
129
|
+
styles[md[STYLE_NAME]] = md[STYLE_TYPE]
|
130
|
+
remaining = md.post_match
|
131
|
+
else
|
132
|
+
remaining = ""
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
remaining = code
|
137
|
+
result = String.new
|
138
|
+
|
139
|
+
# Then make a RE that includes the ERb-related styles.
|
140
|
+
# Match positions:
|
141
|
+
#
|
142
|
+
# 1. ROW_START Begin table row ?
|
143
|
+
# 2. TYPE ERb text style type
|
144
|
+
# 3. ERB_CODE ERb code
|
145
|
+
# 4. ROW_END End table row (empty cells then end of row) ?
|
146
|
+
#
|
147
|
+
# "?": optional, might not occur every time
|
148
|
+
re_erb = /(<table:table-row[^>]*>)?<table:table-cell [^>]*table:style-name="(#{styles.keys.join '|'})"[^>]*><text:p>([^<]*)<\/text:p><\/table:table-cell>(((<table:covered-table-cell[^\/>]*\/>)|(<table:table-cell[^\/>]*\/>))*<\/table:table-row>)?/
|
149
|
+
|
150
|
+
# The text one:
|
151
|
+
# re_erb = /(<table:table-row[^>]*>)?<table:table-cell [^>]+>)?(<text:list-item>)?(<text:p [^>]+>)?(<\/text:span>)?<text:span text:style-name="(#{styles.keys.join '|'})">(([^<]*|<text:line-break\/>|<text:tab\/>)+)<\/text:span>(<text:span [^>]+>)?(<\/text:p>)?(<\/text:list-item>)?(<\/table:table-cell>(<table:covered-table-cell\/>)*<\/table:table-row>)?/
|
152
|
+
|
153
|
+
# Then search for all text using those styles
|
154
|
+
while remaining.length > 0
|
155
|
+
|
156
|
+
md = re_erb.match remaining
|
157
|
+
|
158
|
+
if md
|
159
|
+
|
160
|
+
result += md.pre_match
|
161
|
+
|
162
|
+
match_code = false
|
163
|
+
match_row = false
|
164
|
+
|
165
|
+
if styles[md[TYPE]] == 'Code'
|
166
|
+
match_code = true
|
167
|
+
delim_start = '<% ' ; delim_end = ' %>'
|
168
|
+
if md[ROW_START] and md[ROW_END]
|
169
|
+
match_row = true
|
170
|
+
end
|
171
|
+
else # style is Value or Literal
|
172
|
+
if styles[md[TYPE]] == 'Literal'
|
173
|
+
delim_start = '<%= ' ; delim_end = ' %>'
|
174
|
+
else
|
175
|
+
delim_start = '<table:table-cell table:style-name="Default" office:value-type="string"><text:p><%= ERB::Util.h('
|
176
|
+
delim_end = ') %></text:p></table:table-cell>'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
if md[ROW_START] and not match_row
|
181
|
+
result += md[ROW_START]
|
182
|
+
end
|
183
|
+
|
184
|
+
result += "#{delim_start}#{self.unnormalize md[ERB_CODE]}#{delim_end}"
|
185
|
+
|
186
|
+
if md[ROW_END] and not match_row
|
187
|
+
result += md[ROW_END]
|
188
|
+
end
|
189
|
+
|
190
|
+
remaining = md.post_match
|
191
|
+
|
192
|
+
else # no further matches
|
193
|
+
result += remaining
|
194
|
+
remaining = ""
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
return result
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# OOo has a queer way of storing style information for cells. In
|
203
|
+
# some cases it is in the cell's attribute "table:style-name", but
|
204
|
+
# the default style for cells is also stored in the columns
|
205
|
+
# section at the beginning of each sheet. So there's no way of
|
206
|
+
# knowing in advance whether a cell will have its style specified
|
207
|
+
# or whether it has to be implied from the column definitions.
|
208
|
+
|
209
|
+
# This method regularises the cell styles: it takes the style
|
210
|
+
# definitions from each sheet's column definitions and applies
|
211
|
+
# them to any cells where the style is not specified. The result
|
212
|
+
# is a still-valid XML document but with explicit styles on each
|
213
|
+
# cell. This makes the document easier to compile.
|
214
|
+
|
215
|
+
def regularise_styles(content_raw)
|
216
|
+
doc = REXML::Document.new(content_raw)
|
217
|
+
|
218
|
+
# Get the default column types from all the sheets (tables) in the workbook
|
219
|
+
num_tables = doc.root.elements.to_a('//office:body/*/table:table').length
|
220
|
+
(1 .. num_tables).to_a.each do |tnum|
|
221
|
+
col_types = []
|
222
|
+
cols = doc.root.elements.to_a("//table:table[#{tnum}]/table:table-column")
|
223
|
+
cols.each do |col|
|
224
|
+
(0 ... (col.attributes['table:number-columns-repeated'] || 1).to_i).to_a.each do
|
225
|
+
col_types << col.attributes['table:default-cell-style-name']
|
226
|
+
end
|
227
|
+
end # each column
|
228
|
+
|
229
|
+
# Get the number of rows for each table
|
230
|
+
num_rows = doc.root.elements.to_a("//table:table[#{tnum}]/table:table-row").length
|
231
|
+
|
232
|
+
# Go through each row and process its cells
|
233
|
+
(1 .. num_rows).to_a.each do |rnum|
|
234
|
+
# The cells are both <table:table-cell> and <table:covered-table-cell>
|
235
|
+
cells = doc.root.elements.to_a(<<-END
|
236
|
+
//table:table[#{tnum}]/table:table-row[#{rnum}]/(table:table-cell | table:covered-table-cell)
|
237
|
+
END
|
238
|
+
)
|
239
|
+
# Keep track of the column number, for formatting purposes (c.f. col_types)
|
240
|
+
col_num = 0
|
241
|
+
cells.each do |cell|
|
242
|
+
# Only need to explicitly format the <table:table-cell>s
|
243
|
+
if cell.name == 'table-cell'
|
244
|
+
cell.attributes['table:style-name'] ||= col_types[col_num]
|
245
|
+
end
|
246
|
+
# Advance the column number, based on the columns spanned by the cell
|
247
|
+
col_num += (cell.attributes['table:number-columns-repeated'] || 1).to_i
|
248
|
+
end
|
249
|
+
|
250
|
+
end # each row
|
251
|
+
end # each table
|
252
|
+
|
253
|
+
return doc.to_s
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
@@ -29,7 +29,18 @@ module Documatic
|
|
29
29
|
%Q(<text:span text:style-name="#{stylename}">#{ERB::Util.h(content)}</text:span>)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
# Turns an array of strings into a single, escaped string with
|
33
|
+
# OpenDocument line breaks (<text:line-break/>), omitting any
|
34
|
+
# blank lines. Perfect for address blocks etc.
|
35
|
+
def line_break(lines)
|
36
|
+
lines_esc = lines.collect do |line|
|
37
|
+
ERB::Util.h(line)
|
38
|
+
end
|
39
|
+
return (lines_esc.find_all do |line|
|
40
|
+
line && line.to_s.length > 0
|
41
|
+
end).join('<text:line-break/>')
|
42
|
+
end
|
43
|
+
|
33
44
|
# Inserts a partial into the document at the chosen position.
|
34
45
|
# This helper should be invoked from within "Ruby Block" because
|
35
46
|
# it inserts unescaped block-level material in the current
|
@@ -77,7 +77,7 @@ module Documatic::OpenDocumentText
|
|
77
77
|
body_text = doc.root.elements['office:body/office:text']
|
78
78
|
body_text.elements.delete('text:sequence-decls')
|
79
79
|
body_text.elements.delete('office:forms')
|
80
|
-
@content_erb = self.erbify( (body_text.elements.to_a.collect do |e| e.to_s ; end ).join )
|
80
|
+
@content_erb = self.erbify( (body_text.elements.to_a.collect do |e| e.to_s ; end ).join("\n") )
|
81
81
|
self.jar.get_output_stream('documatic/partial/content.erb') do |f|
|
82
82
|
f.write @content_erb
|
83
83
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'rexml/text'
|
1
|
+
#require 'rexml/text'
|
2
|
+
require 'rexml/document'
|
2
3
|
require 'rexml/attribute'
|
3
4
|
require 'zip/zip'
|
4
5
|
require 'erb'
|
@@ -39,7 +40,7 @@ module Documatic::OpenDocumentText
|
|
39
40
|
|
40
41
|
SPAN_END = 4
|
41
42
|
SPAN_START = 8
|
42
|
-
|
43
|
+
|
43
44
|
# Match types:
|
44
45
|
TABLE_ROW = 1
|
45
46
|
PARAGRAPH = 2
|
@@ -67,11 +68,11 @@ module Documatic::OpenDocumentText
|
|
67
68
|
end
|
68
69
|
|
69
70
|
def process_template(args, &block)
|
70
|
-
if args[:options] && args[:options].
|
71
|
-
output_dir = File.dirname(args[:options].
|
71
|
+
if args[:options] && args[:options].template_file && args[:options].output_file
|
72
|
+
output_dir = File.dirname(args[:options].output_file)
|
72
73
|
File.directory?(output_dir) || FileUtils.mkdir_p(output_dir)
|
73
|
-
FileUtils.cp(args[:options].
|
74
|
-
template = self.new(args[:options].
|
74
|
+
FileUtils.cp(args[:options].template_file, args[:options].output_file)
|
75
|
+
template = self.new(args[:options].output_file)
|
75
76
|
template.process :data => args[:data], :options => args[:options]
|
76
77
|
template.save
|
77
78
|
if block
|
@@ -80,7 +81,7 @@ module Documatic::OpenDocumentText
|
|
80
81
|
end
|
81
82
|
template.close
|
82
83
|
else
|
83
|
-
raise ArgumentError, 'Need to specify both :
|
84
|
+
raise ArgumentError, 'Need to specify both :template_file and :output_file in options'
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -125,8 +126,8 @@ module Documatic::OpenDocumentText
|
|
125
126
|
|
126
127
|
def compile
|
127
128
|
# Read the raw files
|
128
|
-
@content_raw =
|
129
|
-
@styles_raw =
|
129
|
+
@content_raw = pretty_xml('content.xml')
|
130
|
+
@styles_raw = pretty_xml('styles.xml')
|
130
131
|
|
131
132
|
@content_erb = self.erbify(@content_raw)
|
132
133
|
@styles_erb = self.erbify(@styles_raw)
|
@@ -145,10 +146,20 @@ module Documatic::OpenDocumentText
|
|
145
146
|
|
146
147
|
protected
|
147
148
|
|
149
|
+
def pretty_xml(filename)
|
150
|
+
# Pretty print the XML source
|
151
|
+
xml_doc = REXML::Document.new(self.jar.read(filename))
|
152
|
+
xml_text = String.new
|
153
|
+
# xml_doc.write(xml_text, Documatic.debug ? 0 : -1)
|
154
|
+
xml_doc.write(xml_text, 0)
|
155
|
+
return xml_text
|
156
|
+
end
|
157
|
+
|
148
158
|
# Change OpenDocument line breaks and tabs in the ERb code to regular characters.
|
149
159
|
def unnormalize(code)
|
150
160
|
code = code.gsub(/<text:line-break\/>/, "\n")
|
151
161
|
code = code.gsub(/<text:tab\/>/, "\t")
|
162
|
+
code = code.gsub(/<text:s(\/|(\s[^>]*))>/, " ")
|
152
163
|
return REXML::Text.unnormalize(code)
|
153
164
|
end
|
154
165
|
|
@@ -158,8 +169,8 @@ module Documatic::OpenDocumentText
|
|
158
169
|
remaining = code
|
159
170
|
styles = {'Ruby_20_Code' => 'Code', 'Ruby_20_Value' => 'Value',
|
160
171
|
'Ruby_20_Block' => 'Block', 'Ruby_20_Literal' => 'Literal'}
|
161
|
-
re_styles = /<style:style style:name="([^"]+)" style:
|
162
|
-
|
172
|
+
re_styles = /<style:style style:name="([^"]+)"[^>]* style:parent-style-name="Ruby_20_(Code|Value|Block|Literal)"[^>]*>/
|
173
|
+
|
163
174
|
while remaining.length > 0
|
164
175
|
md = re_styles.match remaining
|
165
176
|
if md
|
@@ -180,6 +191,7 @@ module Documatic::OpenDocumentText
|
|
180
191
|
# 2. ITEM_START Begin list item ?
|
181
192
|
# 3. PARA_START Begin paragraph ?
|
182
193
|
# 4. SPAN_END Another text span ends immediately before ERb ?
|
194
|
+
# --5. SPACE (possible leading space)
|
183
195
|
# 5. TYPE ERb text style type
|
184
196
|
# 6. ERB_CODE ERb code
|
185
197
|
# 7. (ERb inner brackets)
|
@@ -189,7 +201,8 @@ module Documatic::OpenDocumentText
|
|
189
201
|
# 11. ROW_END End table row (incl. covered rows) ?
|
190
202
|
#
|
191
203
|
# "?": optional, might not occur every time
|
192
|
-
re_erb = /(<table:table-row[^>]
|
204
|
+
# re_erb = /(<table:table-row[^>]*>\s*<table:table-cell [^>]+>\s*)?(\s*<text:list-item>\s*)?\s*(<text:p [^>]+>\s*)?(<\/text:span>)?<text:span text:style-name="(#{styles.keys.join '|'})">(([^<]*|<text:line-break\/>|<text:tab\/>)+)<\/text:span>(<text:span [^>]+>)?(\s*<\/text:p>\s*)?(<\/text:list-item>\s*)?(<\/table:table-cell>\s*(<table:covered-table-cell\/>\s*)*<\/table:table-row>)?/
|
205
|
+
re_erb = /(<table:table-row[^>]*>\s*<table:table-cell [^>]+>\s*)?(<text:list-item>\s*)?(<text:p [^>]+>\s*)?(<\/text:span>\s*)?<text:span text:style-name="(#{styles.keys.join '|'})">(([^<]*|<text:line-break\/>|<text:tab\/>)+)<\/text:span>(<text:span [^>]+>)?(\s*<\/text:p>)?(\s*<\/text:list-item>)?(\s*<\/table:table-cell>(\s*<table:covered-table-cell\/>)*\s*<\/table:table-row>)?/
|
193
206
|
|
194
207
|
# Then search for all text using those styles
|
195
208
|
while remaining.length > 0
|
@@ -197,7 +210,6 @@ module Documatic::OpenDocumentText
|
|
197
210
|
md = re_erb.match remaining
|
198
211
|
|
199
212
|
if md
|
200
|
-
|
201
213
|
result += md.pre_match
|
202
214
|
|
203
215
|
match_code = false
|
@@ -253,6 +265,9 @@ module Documatic::OpenDocumentText
|
|
253
265
|
result += md[SPAN_END]
|
254
266
|
end
|
255
267
|
else
|
268
|
+
#if md[SPACE]
|
269
|
+
# result += md[SPACE]
|
270
|
+
#end
|
256
271
|
if md[SPAN_START] and not md[SPAN_END]
|
257
272
|
result += md[SPAN_START]
|
258
273
|
end
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: documatic
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0
|
7
|
-
date: 2007-
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-09-02 00:00:00 +10:00
|
8
8
|
summary: Documatic is an OpenDocument extension for Ruby Reports (Ruport). It is a template-driven formatter that can be used to produce attractive printable documents such as database reports, invoices, letters, faxes and more.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,17 +29,20 @@ post_install_message:
|
|
29
29
|
authors: []
|
30
30
|
|
31
31
|
files:
|
32
|
-
- lib
|
33
|
-
- lib/documatic
|
32
|
+
- lib/
|
34
33
|
- lib/documatic.rb
|
34
|
+
- lib/documatic/
|
35
35
|
- lib/documatic/component.rb
|
36
|
-
- lib/documatic/
|
36
|
+
- lib/documatic/formatter/
|
37
|
+
- lib/documatic/formatter/open_document.rb
|
38
|
+
- lib/documatic/init.rb
|
39
|
+
- lib/documatic/open_document_spreadsheet/
|
40
|
+
- lib/documatic/open_document_spreadsheet/helper.rb
|
41
|
+
- lib/documatic/open_document_spreadsheet/template.rb
|
42
|
+
- lib/documatic/open_document_text/
|
37
43
|
- lib/documatic/open_document_text/helper.rb
|
38
44
|
- lib/documatic/open_document_text/partial.rb
|
39
45
|
- lib/documatic/open_document_text/template.rb
|
40
|
-
- lib/documatic/formatter
|
41
|
-
- lib/documatic/formatter/open_document.rb
|
42
|
-
- lib/documatic/init.rb
|
43
46
|
- tests
|
44
47
|
- README
|
45
48
|
test_files: []
|
@@ -73,7 +76,7 @@ dependencies:
|
|
73
76
|
requirements:
|
74
77
|
- - ">="
|
75
78
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
79
|
+
version: 0.2.2
|
77
80
|
version:
|
78
81
|
- !ruby/object:Gem::Dependency
|
79
82
|
name: ruport
|
@@ -82,5 +85,5 @@ dependencies:
|
|
82
85
|
requirements:
|
83
86
|
- - ">="
|
84
87
|
- !ruby/object:Gem::Version
|
85
|
-
version: 1.
|
88
|
+
version: 1.2.0
|
86
89
|
version:
|