mrtexport 0.0.3

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 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: []