mrtexport 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d20d61ba920b60a63f93e98d6e2fd3d8a89a4414
4
+ data.tar.gz: 219915b00d28a897167ffaa34d23530049525a2f
5
+ SHA512:
6
+ metadata.gz: 0bd1c6eca3cceb8e8ff5ae47a6dcb1b79fd5a96229d6c662df4af3d3ff974aae20a6dd52032ee8b83b861386f873926a1fa2b9dccf599ee1920d3584d8416da3
7
+ data.tar.gz: 8214d9e736f2b4f18e487b117942b8fa32b3e0a6d42f29ecd5b3de8357b7d457b3987031958cfd69d17df5bae7f6029349ae9abe01bc200e26cd8bdce9ed4311
data/lib/data_band.rb ADDED
@@ -0,0 +1,74 @@
1
+ require_relative "util.rb"
2
+
3
+ class DataBandRenderer
4
+ def initialize(data, pdf, sql_conn, replacements, data_sources, y_off)
5
+ @data = data
6
+ @pdf = pdf
7
+ @sql_conn = sql_conn
8
+ @data_sources = data_sources
9
+ @replacements = replacements
10
+ @y_off = y_off
11
+
12
+ @row_data = prepare_dataset
13
+ end
14
+
15
+ def render
16
+ @row_data.each do |row|
17
+ draw_row(row)
18
+ end
19
+
20
+ return @y_off
21
+ end
22
+
23
+ def prepare_dataset
24
+ data_source = @data.xpath("DataSourceName").text
25
+ sql = @data_sources[data_source]["sql"]
26
+
27
+ @replacements.each do |key, val|
28
+ sql.sub!("{#{key}}", val)
29
+ end
30
+
31
+ return @sql_conn.query sql
32
+ end
33
+
34
+ def draw_row(row)
35
+ last_y_off = @y_off
36
+
37
+ @data.xpath("Components/*[contains(.,'Text')]").each do |node|
38
+ next if node.name == "Name"
39
+
40
+ if node.name.start_with? "Text"
41
+ x, y, w, h = Stylist.get_dimensions(node, @y_off)
42
+
43
+ # Text
44
+ text = node.xpath("Text").text
45
+
46
+ # SQL replacements
47
+ if text.include?("{")
48
+ text.gsub!(/{.*}/) do |m|
49
+ index = m.split(".")[1].sub("}","")
50
+ text = row[index]
51
+ text = Util.number_format(text) if Util.is_number?(text)
52
+ text
53
+ end
54
+ end
55
+
56
+ # Do painting
57
+ @pdf.fill_color Stylist.get_text_colour(node)
58
+ @pdf.text_box text,
59
+ :at => [x + 1, y - 1],
60
+ :height => h,
61
+ :width => w,
62
+ :align => Stylist.get_horizontal_align(node),
63
+ :valign => Stylist.get_vertical_align(node),
64
+ :size => Stylist.get_font_size(node),
65
+ :font => Stylist.get_font_face(node),
66
+ :style => Stylist.get_font_style(node)
67
+
68
+ last_y_off = (720 + h - y) if (720 + h - y) > @y_off
69
+ end
70
+ end
71
+
72
+ @y_off = last_y_off
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ class DatabaseDatabaseBuilder
2
+ def DatabaseDatabaseBuilder.get_database_database(document)
3
+ connections = {}
4
+
5
+ document.xpath("//Dictionary/Databases/*").each do |database|
6
+ name = database.xpath("Name").text
7
+ connection_string = database.xpath("ConnectionString").text
8
+
9
+ connections[name] = connection_string
10
+ end
11
+
12
+ return connections
13
+ end
14
+
15
+ def DatabaseDatabaseBuilder.get_data_sources(doc)
16
+ data_sources = {}
17
+
18
+ data_source_container = doc.xpath("//DataSources")
19
+ data_source_count = data_source_container.attribute("count")
20
+
21
+ data_source_container.xpath("./*").each do |data_source_node|
22
+ # Data source name
23
+ name = data_source_node.xpath("Name").text
24
+
25
+ # Column names
26
+ column_container = data_source_node.xpath("Columns")
27
+ columns = []
28
+ column_container.xpath("./value").each do |column|
29
+ columns << column.text.split(",")[0]
30
+ end
31
+
32
+ # SQL query
33
+ sql = data_source_node.xpath("SqlCommand").text
34
+ sql.gsub!("\n", " ")
35
+
36
+ data_source = {}
37
+ data_source["columns"] = columns
38
+ data_source["sql"] = sql
39
+
40
+ data_sources[name] = data_source
41
+ end
42
+
43
+ return data_sources
44
+ end
45
+ end
data/lib/mrtexport.rb ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Dependencies
4
+ require "prawn"
5
+ require "nokogiri"
6
+ require "mysql2"
7
+ require "base64"
8
+
9
+ # Included files
10
+ require_relative "dbdb_builder"
11
+ require_relative "data_band"
12
+ require_relative "stylist"
13
+
14
+ class MRTExport
15
+ def initialize(params)
16
+ @report_file = params[:report_file]
17
+ @output_file = params[:output_file]
18
+ @export_format = params[:export_format]
19
+ @replacements = params[:replacements]
20
+
21
+ puts "[+] Compiling new report: #{@report_file}"
22
+
23
+ @xml_doc = Nokogiri::XML(File.open(@report_file))
24
+
25
+ @sql_connections = DatabaseDatabaseBuilder.get_database_database(@xml_doc)
26
+ @data_sources = DatabaseDatabaseBuilder.get_data_sources(@xml_doc)
27
+
28
+ puts @sql_connections
29
+
30
+ initialize_database_connection if @sql_connections.length > 0
31
+
32
+ @y_off = 0
33
+
34
+ generate_pdf
35
+ end
36
+
37
+ def initialize_database_connection
38
+ sql_connection_string = @sql_connections["Localhost"]
39
+ sql_connection_array = {}
40
+
41
+ sql_connection_string.split(";").each do |x|
42
+ sql_connection_array[x.split("=")[0]] = x.split("=")[1]
43
+ end
44
+
45
+ @sql_connection = Mysql2::Client.new(
46
+ :host => sql_connection_array["Server"],
47
+ :username => sql_connection_array["User"],
48
+ :password => sql_connection_array["Password"],
49
+ :database => sql_connection_array["Database"]
50
+ )
51
+ end
52
+
53
+ def get_value_from_db(name)
54
+ name.sub! "{", ""
55
+ name.sub! "}", ""
56
+
57
+ query_parts = name.split(".")
58
+ query_name = query_parts[0]
59
+ query_field = query_parts[1]
60
+
61
+ sql = @data_sources[query_name]["sql"]
62
+
63
+ @replacements.each do |key, val|
64
+ sql.sub!("{#{key}}", val)
65
+ end
66
+
67
+ text = ""
68
+
69
+ rs = @sql_connection.query sql
70
+ rs.each do |r|
71
+ text = r[query_field]
72
+ text = Util.number_format(text) if Util.is_number?(text)
73
+ end
74
+
75
+ return text
76
+ end
77
+
78
+ def generate_pdf
79
+ @pdf = Prawn::Document.new
80
+
81
+ @xml_doc.xpath("//Pages/*").each do |page|
82
+ # List bands bands
83
+ puts "[*] listing report bands"
84
+ page.xpath("./Components/*[contains(., 'Band')]").each do |band|
85
+ puts " - #{band.name}"
86
+ end
87
+
88
+ # render out of band stuff
89
+ page.xpath("./Components/*[not(contains(.,'Band'))]").each do |component|
90
+ if component.name.include?("Text")
91
+ render_background(component)
92
+ render_text(component)
93
+ elsif component.name.include?("Image")
94
+ render_image(component)
95
+ end
96
+ end
97
+
98
+ # but we want to do bands in a specific order!
99
+ # Start with header band
100
+ page.xpath("./Components/*[contains(., 'ReportTitleBand')]").each do |band|
101
+ puts "[*] Doing band: #{band.name}, ref: #{band.attribute("Ref")}"
102
+ render_band(band)
103
+ end
104
+
105
+ page.xpath("./Components/*[contains(., 'HeaderBand')]").each do |band|
106
+ puts "[*] Doing band: #{band.name}, ref: #{band.attribute("Ref")}"
107
+ render_band(band)
108
+ end
109
+
110
+ page.xpath("./Components/*[contains(., 'DataBand')]").each do |band|
111
+ puts "[*] Doing band: #{band.name}, ref: #{band.attribute("Ref")}"
112
+ #render_band(band)
113
+ data_band_renderer = DataBandRenderer.new(
114
+ band,
115
+ @pdf,
116
+ @sql_connection,
117
+ @replacements,
118
+ @data_sources,
119
+ @y_off
120
+ )
121
+
122
+ @y_off = data_band_renderer.render
123
+ end
124
+
125
+ page.xpath("./Components/*[contains(., 'FooterBand')]").each do |band|
126
+ puts "[*] Doing band: #{band.name}, ref: #{band.attribute("Ref")}"
127
+ render_band(band)
128
+ end
129
+ end
130
+
131
+ @pdf.render_file @output_file
132
+ end
133
+
134
+ def render_band(band)
135
+ render_backgrounds(band)
136
+ render_images(band)
137
+ render_texts(band)
138
+ end
139
+
140
+ def render_backgrounds(page)
141
+ page.xpath("Components/*[contains(.,'Text')]").each do |node|
142
+ next if node.name == "Name"
143
+
144
+ if node.name.start_with? "Text"
145
+ x, y, w, h = Stylist.get_dimensions(node, @y_off)
146
+
147
+ hue = node.xpath("Brush").text
148
+
149
+ if hue != "Transparent"
150
+ hue = hue.sub("[","").sub("]","").split(":")
151
+ hex_hue = hue[0].to_i.to_s(16) << hue[1].to_i.to_s(16) << hue[2].to_i.to_s(16)
152
+
153
+ @pdf.fill_color hex_hue
154
+ @pdf.fill_rectangle [x,y], w, h
155
+ end
156
+
157
+ if node.xpath("Border").text != ""
158
+ border = node.xpath("Border").text
159
+ border_bits = border.split(";")
160
+
161
+ sides = border_bits[0]
162
+
163
+ if sides != "None"
164
+ @pdf.stroke_color = Stylist.mrt_colour_to_hex(border_bits[1])
165
+ @pdf.line_width = 1
166
+
167
+ @pdf.line [x, y], [x+w, y] if sides.include? "Top"
168
+ @pdf.line [x, y-h], [x+w, y-h] if sides.include? "Bottom"
169
+ @pdf.line [x, y], [x, y-h] if sides.include? "Left"
170
+ @pdf.line [x+w, y], [x+w, y-h] if sides.include? "Right"
171
+ @pdf.stroke
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ def render_background(node)
179
+ return if node.name == "Name"
180
+
181
+ if node.name.start_with? "Text"
182
+ x, y, w, h = Stylist.get_dimensions(node, @y_off)
183
+
184
+ hue = node.xpath("Brush").text
185
+
186
+ if hue != "Transparent"
187
+ hue = hue.sub("[","").sub("]","").split(":")
188
+ hex_hue = hue[0].to_i.to_s(16) << hue[1].to_i.to_s(16) << hue[2].to_i.to_s(16)
189
+
190
+ @pdf.fill_color hex_hue
191
+ @pdf.fill_rectangle [x,y], w, h
192
+ end
193
+
194
+ if node.xpath("Border").text != ""
195
+ border = node.xpath("Border").text
196
+ border_bits = border.split(";")
197
+
198
+ sides = border_bits[0]
199
+
200
+ if sides != "None"
201
+ @pdf.stroke_color = Stylist.mrt_colour_to_hex(border_bits[1])
202
+ @pdf.line_width = 1
203
+
204
+ @pdf.line [x, y], [x+w, y] if sides.include? "Top"
205
+ @pdf.line [x, y-h], [x+w, y-h] if sides.include? "Bottom"
206
+ @pdf.line [x, y], [x, y-h] if sides.include? "Left"
207
+ @pdf.line [x+w, y], [x+w, y-h] if sides.include? "Right"
208
+ @pdf.stroke
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def render_images(band)
215
+ band.xpath("Components/*[contains(.,'Image')]").each do |node|
216
+ render_image(node)
217
+ end
218
+ end
219
+
220
+ def render_image(node)
221
+ x, y, w, h = Stylist.get_dimensions(node, @y_off)
222
+
223
+ data = Base64::decode64(node.xpath("Image").text)
224
+
225
+ File.open("/tmp/mrt_to_pdf_img",'w') { |file| file.puts data }
226
+
227
+ @pdf.image "/tmp/mrt_to_pdf_img", :at => [x, 0 + h], :width => w
228
+
229
+ File.delete("/tmp/mrt_to_pdf_img")
230
+ end
231
+
232
+ def render_texts(page)
233
+ last_y_off = @y_off
234
+
235
+ page.xpath("Components/*[contains(.,'Text')]").each do |node|
236
+ last_y_off = render_text(node)
237
+ end
238
+
239
+ @y_off = last_y_off
240
+ end
241
+
242
+ def render_text(node)
243
+ last_y_off = @y_off
244
+
245
+ return if node.name == "Name"
246
+
247
+ if node.name.start_with? "Text"
248
+ x, y, w, h = Stylist.get_dimensions(node, @y_off)
249
+
250
+ # Text
251
+ text = node.xpath("Text").text
252
+
253
+ # SQL replacements
254
+ if text.include?("{")
255
+ text.gsub!(/{.*}/) { |m| get_value_from_db(m) }
256
+ end
257
+
258
+ # Do painting
259
+ @pdf.fill_color = Stylist.get_text_colour(node)
260
+ @pdf.text_box text,
261
+ :at => [x + 1, y - 1],
262
+ :height => h,
263
+ :width => w,
264
+ :align => Stylist.get_horizontal_align(node),
265
+ :valign => Stylist.get_vertical_align(node),
266
+ :size => Stylist.get_font_size(node),
267
+ :font => Stylist.get_font_face(node),
268
+ :style => Stylist.get_font_style(node)
269
+
270
+ last_y_off = (740 - y) if (740 - y) > @y_off
271
+ end
272
+
273
+ return last_y_off
274
+ end
275
+ end
276
+
277
+ # Handler for command line launch
278
+ if __FILE__ == $0
279
+ if ARGV[1].nil?
280
+ puts "USAGE:"
281
+ puts "./mrttopdf.rb <report file> <output file> <replacement 1> .. <replacement n>"
282
+ exit
283
+ elsif ARGV[1] == "--version"
284
+ puts "0.0.1"
285
+ end
286
+
287
+ report_file = ARGV[0]
288
+ output_file = ARGV[1]
289
+
290
+ replacements = {}
291
+
292
+ ARGV[2..ARGV.length].each do |arg|
293
+ arg_bits = arg.split("=")
294
+ replacements[arg_bits[0]] = arg_bits[1]
295
+ end
296
+
297
+ MRTExport.new({
298
+ :report_file => report_file,
299
+ :output_file => output_file,
300
+ :export_format => "pdf",
301
+ :replacements => replacements
302
+ })
303
+ end
data/lib/stylist.rb ADDED
@@ -0,0 +1,61 @@
1
+ class Stylist
2
+ def Stylist.mrt_colour_to_hex(mrt_colour)
3
+ mrt_colour = mrt_colour.sub("[","").sub("]","").split(":")
4
+ return mrt_colour[0].to_i.to_s(16) << mrt_colour[1].to_i.to_s(16) << mrt_colour[2].to_i.to_s(16)
5
+ end
6
+
7
+ def Stylist.get_text_colour(node)
8
+ hue = node.xpath("TextBrush").text
9
+ case hue
10
+ when "Black"
11
+ "000000"
12
+ else
13
+ Stylist.mrt_colour_to_hex(hue)
14
+ end
15
+ end
16
+
17
+ def Stylist.get_horizontal_align(node)
18
+ if node.xpath("HorAlignment").text != ""
19
+ return node.xpath("HorAlignment").text.downcase.to_sym
20
+ else
21
+ return :left
22
+ end
23
+ end
24
+
25
+ def Stylist.get_vertical_align(node)
26
+ if node.xpath("VertAlignment").text != ""
27
+ return node.xpath("VertAlignment").text.downcase.to_sym
28
+ else
29
+ return :top
30
+ end
31
+ end
32
+
33
+ def Stylist.get_font_face(node)
34
+ node.xpath("Font").text.split(",")[0]
35
+ end
36
+
37
+ def Stylist.get_font_size(node)
38
+ node.xpath("Font").text.split(",")[1].to_i
39
+ end
40
+
41
+ def Stylist.get_font_style(node)
42
+ font_style = node.xpath("Font").text.split(",")[2]
43
+ if font_style.nil?
44
+ return :normal
45
+ else
46
+ return font_style.downcase.to_sym
47
+ end
48
+ end
49
+
50
+ def Stylist.get_dimensions(node, y_off)
51
+ position = node.xpath("ClientRectangle").text.split(",")
52
+ position = position.map {|x| x.to_f}
53
+
54
+ x = Util.cm_to_px(position[0])
55
+ y = Util.trans_y(y_off, Util.cm_to_px(position[1]))
56
+ w = Util.cm_to_px(position[2])
57
+ h = Util.cm_to_px(position[3])
58
+
59
+ return x, y, w, h
60
+ end
61
+ end
data/lib/util.rb ADDED
@@ -0,0 +1,17 @@
1
+ class Util
2
+ def Util.cm_to_px(cm)
3
+ return cm / 2.5 * 72
4
+ end
5
+
6
+ def Util.trans_y(off, y)
7
+ 720 - off - y
8
+ end
9
+
10
+ def Util.is_number?(object)
11
+ true if Float(object) rescue false
12
+ end
13
+
14
+ def Util.number_format(number)
15
+ '%.2f' % number.to_f
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mrtexport
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - jsrn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: prawn
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: base64
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: An exporter/renderer for .mrt report files.
70
+ email: james.srn@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/data_band.rb
76
+ - lib/dbdb_builder.rb
77
+ - lib/mrtexport.rb
78
+ - lib/stylist.rb
79
+ - lib/util.rb
80
+ homepage: https://github.com/jsrn/MRTExport
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: A renderer for MRT files.
104
+ test_files: []