xhtml_report_generator 3.0.3 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -13
- data/lib/xhtml_report_generator/custom.rb +197 -18
- data/lib/xhtml_report_generator/version.rb +1 -1
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3544c56a29c66abcc6c6ba3fb11ddc204b3198a
|
4
|
+
data.tar.gz: aeb8c4b728b729c3d88e428297dc5f13f5871ecf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 102f3476494f50f9d96d5a735fe33ae4714d74a70f422b47c0930c613b891d724b23e5ec59624a9361cc21f3e2982bf626bea5c15d772470fc1421760802aa58
|
7
|
+
data.tar.gz: f926db10047c34ddd95eaeb1a9eb7c0f0378cbfec795bc3999e4d8ce28478f813dd4284c7a4e41e97e8e15eb926aa3998eab84c0c3a15cbd96c6b9d2e12080aa
|
data/README.md
CHANGED
@@ -48,9 +48,73 @@ By default "custom.rb" is loaded through instance eval, see
|
|
48
48
|
[XhtmlReportGenerator/Generator](http://www.rubydoc.info/gems/xhtml_report_generator/XhtmlReportGenerator/Generator)
|
49
49
|
for the documentation of available methods.
|
50
50
|
|
51
|
+
Advanced example1: custom tables including pictures or links
|
52
|
+
----------------------------------
|
53
|
+
[Code] (../master/test/test.rb#L166-L233)
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'xhtml_report_generator'
|
57
|
+
gen1 = XhtmlReportGenerator::Generator.new
|
58
|
+
gen1.create_layout("Custom Table")
|
59
|
+
gen1.heading("h1") {"custom styling"}
|
60
|
+
table_data = [
|
61
|
+
(0..9).collect{|i| i},
|
62
|
+
(10..19).collect{|i| i},
|
63
|
+
(20..29).collect{|i| i},
|
64
|
+
(30..39).collect{|i| i},
|
65
|
+
]
|
66
|
+
table_opts = {
|
67
|
+
:headers => 3,
|
68
|
+
:data_is_xhtml => false,
|
69
|
+
:special => [
|
70
|
+
{ # highlight all numbers from 0-13 green, only 11-13 should be green since the others are part of heading
|
71
|
+
condition: Proc.new { |e| (0 <= e.to_i) && (e.to_i <= 13) }, # a proc
|
72
|
+
attributes: {"style" => "background: #00FF00;"},
|
73
|
+
},
|
74
|
+
{ # font-color the area if number contains a 3
|
75
|
+
row_index: 2..3,
|
76
|
+
col_index: 3..7,
|
77
|
+
condition: Proc.new { |e| e.to_s.match(/3/) }, # a proc
|
78
|
+
attributes: {"style" => "color: red;"},
|
79
|
+
},
|
80
|
+
{ # red border around row 2-3 col with title 8
|
81
|
+
row_title: /[23]/,
|
82
|
+
col_title: "8",
|
83
|
+
condition: Proc.new { |e| true }, # a proc
|
84
|
+
attributes: {"style" => "border: 2px solid red;"},
|
85
|
+
},
|
86
|
+
{ # black border around cell bottom right
|
87
|
+
row_index: 2,
|
88
|
+
col_index: 9,
|
89
|
+
condition: Proc.new { |e| true }, # a proc
|
90
|
+
attributes: {"style" => "border: 2px solid black;"},
|
91
|
+
},
|
92
|
+
]
|
93
|
+
}
|
94
|
+
gen1.custom_table(table_data, table_opts)
|
95
|
+
gen1.heading("h1") {"Table Layout"}
|
96
|
+
table_opts = {
|
97
|
+
:headers => 0,
|
98
|
+
:data_is_xhtml => true,
|
99
|
+
}
|
51
100
|
|
101
|
+
a = gen1.get_code_html() {" blub\nblab\n\t\tblib"}
|
102
|
+
b = gen1.get_image_html("path/to/test.png", {"width" => "55", "height"=> "20", "alt" => "some_interesting_text"})
|
103
|
+
c = gen1.get_content_html() {" leading-whitespace removed"}
|
104
|
+
d = gen1.get_link_html("https://rubygems.org/gems/xhtml_report_generator/") {"download the gem"}
|
52
105
|
|
53
|
-
|
106
|
+
table_data = [
|
107
|
+
[a, d],
|
108
|
+
[c, b]
|
109
|
+
]
|
110
|
+
gen1.custom_table(table_data, table_opts)
|
111
|
+
|
112
|
+
gen1.write("path/to/CustomTable.xhtml")
|
113
|
+
```
|
114
|
+
[Preview](https://cdn.rawgit.com/lumean/xhtml-report-generator/master/test/CustomTableReference.xhtml)
|
115
|
+
|
116
|
+
|
117
|
+
Advanced example2: including some graphs to your reports
|
54
118
|
----------------------------------
|
55
119
|
Due to the xml nature it is also easy to insert SVG graphs / pictures. Check out the svg-graph gem
|
56
120
|
|
@@ -82,16 +146,8 @@ graph = SVG::Graph::Line.new({
|
|
82
146
|
graph.add_data({:data => data_sales_02, :title => 'Sales2002'})
|
83
147
|
graph.add_data({:data => data_sales_03, :title => 'Sales2003'})
|
84
148
|
|
85
|
-
#
|
86
|
-
|
87
|
-
doc = REXML::Document.new(graph.burn())
|
88
|
-
svg = doc.elements["//svg"]
|
89
|
-
out = ''
|
90
|
-
f = REXML::Formatters::Pretty.new(0)
|
91
|
-
f.compact = true
|
92
|
-
f.write(svg, out)
|
93
|
-
|
94
|
-
gen1.html(out)
|
149
|
+
# add the svg to the report
|
150
|
+
gen1.html(graph.burn_svg_only())
|
95
151
|
gen1.write("graph.xhtml")
|
96
152
|
|
97
153
|
```
|
@@ -131,8 +187,6 @@ As a start you can copy the [custom.rb](../master/lib/xhtml_report_generator/cus
|
|
131
187
|
default naming.
|
132
188
|
|
133
189
|
|
134
|
-
|
135
|
-
|
136
190
|
Changes from version 2.x to 3.x
|
137
191
|
-------------------------------
|
138
192
|
The options for the initialize method "XhtmlReportGenerator::Generator.new" changed.
|
@@ -98,8 +98,33 @@ module Custom
|
|
98
98
|
return out
|
99
99
|
end
|
100
100
|
|
101
|
+
# @param elem [REXML::Element]
|
102
|
+
# @return [String]
|
103
|
+
def element_to_string(elem)
|
104
|
+
f = REXML::Formatters::Transitive.new(0) # use Transitive to preserve source formatting (e.g. <pre> tags)
|
105
|
+
out = ""
|
106
|
+
f.write(elem, out)
|
107
|
+
return out
|
108
|
+
end
|
109
|
+
|
110
|
+
# @see #code
|
111
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
112
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
113
|
+
# @return [String] the code including <pre> tags as a string
|
114
|
+
def get_code_html(attrs={}, &block)
|
115
|
+
temp = REXML::Element.new("pre")
|
116
|
+
temp.add_attributes(attrs)
|
117
|
+
raise "Block argument is mandatory" unless block_given?
|
118
|
+
text = encoding_fixer(block.call())
|
119
|
+
temp.add_text(text)
|
120
|
+
element_to_string(temp)
|
121
|
+
end
|
122
|
+
|
101
123
|
# Appends a <pre> node after the @current node
|
102
|
-
# @param attrs [Hash] attributes for the <pre> element
|
124
|
+
# @param attrs [Hash] attributes for the <pre> element. The following classes can be passed as attributes and are predefined with a different
|
125
|
+
# background for your convenience !{"class" => "code0"} (light-blue), !{"class" => "code1"} (red-brown),
|
126
|
+
# !{"class" => "code2"} (light-green), !{"class" => "code3"} (light-yellow). You may also specify your own background
|
127
|
+
# as follows: !{"style" => "background: #FF00FF;"}.
|
103
128
|
# @yieldreturn [String] the text to be added to the <pre> element
|
104
129
|
# @return [REXML::Element] the Element which was just added
|
105
130
|
def code(attrs={}, &block)
|
@@ -113,6 +138,19 @@ module Custom
|
|
113
138
|
return @current
|
114
139
|
end
|
115
140
|
|
141
|
+
# @see #content
|
142
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
143
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
144
|
+
# @return [String] the code including <pre> tags as a string
|
145
|
+
def get_content_html(attrs={}, &block)
|
146
|
+
temp = REXML::Element.new("p")
|
147
|
+
temp.add_attributes(attrs)
|
148
|
+
raise "Block argument is mandatory" unless block_given?
|
149
|
+
text = encoding_fixer(block.call())
|
150
|
+
temp.add_text(text)
|
151
|
+
element_to_string(temp)
|
152
|
+
end
|
153
|
+
|
116
154
|
# Appends a <p> node after the @current node
|
117
155
|
# @param attrs [Hash] attributes for the <p> element
|
118
156
|
# @yieldreturn [String] the text to be added to the <p> element
|
@@ -143,6 +181,20 @@ module Custom
|
|
143
181
|
return @current
|
144
182
|
end
|
145
183
|
|
184
|
+
# @see #link
|
185
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
186
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
187
|
+
# @return [String] the code including <pre> tags as a string
|
188
|
+
def get_link_html(href, attrs={}, &block)
|
189
|
+
temp = REXML::Element.new("a")
|
190
|
+
attrs.merge!({"href" => href})
|
191
|
+
temp.add_attributes(attrs)
|
192
|
+
raise "Block argument is mandatory" unless block_given?
|
193
|
+
text = encoding_fixer(block.call())
|
194
|
+
temp.add_text(text)
|
195
|
+
element_to_string(temp)
|
196
|
+
end
|
197
|
+
|
146
198
|
# Appends a <a href = > node after the @current nodes
|
147
199
|
# @param href [String] this is the
|
148
200
|
# @param attrs [Hash] attributes for the <a> element
|
@@ -160,6 +212,22 @@ module Custom
|
|
160
212
|
return @current
|
161
213
|
end
|
162
214
|
|
215
|
+
# @see #image
|
216
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
217
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
218
|
+
# @return [String] the code including <pre> tags as a string
|
219
|
+
def get_image_html(path, attributes = {})
|
220
|
+
# read image as binary and do a base64 encoding
|
221
|
+
binary_data = Base64.strict_encode64(IO.binread(path))
|
222
|
+
type = File.extname(path).gsub('.', '')
|
223
|
+
# create the element
|
224
|
+
temp = REXML::Element.new("img")
|
225
|
+
# add the picture
|
226
|
+
temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
|
227
|
+
temp.add_attributes(attributes)
|
228
|
+
element_to_string(temp)
|
229
|
+
end
|
230
|
+
|
163
231
|
# @param path [String] absolute or relative path to the image that should be inserted into the report
|
164
232
|
# @param attributes [Hash] attributes for the <img> element, any valid html attributes can be specified
|
165
233
|
# you may specify attributes such "alt", "height", "width"
|
@@ -187,7 +255,7 @@ module Custom
|
|
187
255
|
# highlight_captures then puts a <span> </span> tag around all captures of the regex
|
188
256
|
# NOTE: nested captures are not supported and don't make sense in this context!!
|
189
257
|
# @param regex [Regexp] a regular expression that will be matched
|
190
|
-
# @param color [String]
|
258
|
+
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
191
259
|
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches, by default the last inserted element will be scanned
|
192
260
|
# @return [Fixnum] the number of highlighted captures
|
193
261
|
def highlight_captures(regex, color="y", el = @current)
|
@@ -214,7 +282,14 @@ module Custom
|
|
214
282
|
array
|
215
283
|
}
|
216
284
|
num_matches += positions.length
|
217
|
-
|
285
|
+
if ["y","r","g","b"].include?(color)
|
286
|
+
attr = {"class" => color}
|
287
|
+
elsif color.match(/^#[A-Fa-f0-9]{6}$/)
|
288
|
+
attr = {"style" => "background: #{color};"}
|
289
|
+
else
|
290
|
+
raise "invalid color: #{color}"
|
291
|
+
end
|
292
|
+
replace_text_with_elements(el, i, "span", attr, positions)
|
218
293
|
else
|
219
294
|
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
220
295
|
num_matches += highlight_captures(regex, color, i)
|
@@ -231,7 +306,7 @@ module Custom
|
|
231
306
|
# or match newlines explicitly.
|
232
307
|
# highlight then puts a <span> </span> tag around all matches of regex
|
233
308
|
# @param regex [Regexp] a regular expression that will be matched
|
234
|
-
# @param color [String]
|
309
|
+
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
235
310
|
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches
|
236
311
|
# @return [Fixnum] the number of highlighted captures
|
237
312
|
def highlight(regex, color="y", el = @current)
|
@@ -254,7 +329,14 @@ module Custom
|
|
254
329
|
Regexp.last_match.end(0)-Regexp.last_match.begin(0)]
|
255
330
|
}
|
256
331
|
num_matches += positions.length
|
257
|
-
|
332
|
+
if ["y","r","g","b"].include?(color)
|
333
|
+
attr = {"class" => color}
|
334
|
+
elsif color.match(/^#[A-Fa-f0-9]{6}$/)
|
335
|
+
attr = {"style" => "background: #{color};"}
|
336
|
+
else
|
337
|
+
raise "invalid color: #{color}"
|
338
|
+
end
|
339
|
+
replace_text_with_elements(el, i, "span", attr, positions)
|
258
340
|
else
|
259
341
|
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
260
342
|
# puts "recurse"
|
@@ -272,23 +354,119 @@ module Custom
|
|
272
354
|
# is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3
|
273
355
|
# @return [REXML::Element] the Element which was just added
|
274
356
|
def table(table_data, headers=0, table_attrs={}, tr_attrs={}, th_attrs={}, td_attrs={})
|
275
|
-
|
357
|
+
opts = {
|
358
|
+
headers: headers,
|
359
|
+
data_is_xhtml: false,
|
360
|
+
table_attrs: table_attrs,
|
361
|
+
th_attrs: th_attrs,
|
362
|
+
tr_attrs: tr_attrs,
|
363
|
+
td_attrs: td_attrs,
|
364
|
+
}
|
365
|
+
custom_table(table_data, opts)
|
366
|
+
end
|
367
|
+
|
368
|
+
# creates a html table from two dimensional array of the form Array [row] [col]
|
369
|
+
# @param table_data [Array<Array>] of the form Array [row] [col] containing all data, the '.to_s' method will be called on each element,
|
370
|
+
# @option opts [Number] :headers either of 0, 1, 2, 3. Where 0 is no headers (<th>) at all, 1 is only the first row,
|
371
|
+
# 2 is only the first column and 3 is both, first row and first column as <th> elements. Every other number
|
372
|
+
# is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3
|
373
|
+
# @option opts [Boolean] :data_is_xhtml defaults to false, if true table_data is inserted as xhtml without any sanitation or escaping.
|
374
|
+
# This way a table can be used for custom layouts.
|
375
|
+
# @option opts [Hash] :table_attrs html attributes for the <table> tag
|
376
|
+
# @option opts [Hash] :th_attrs html attributes for the <th> tag
|
377
|
+
# @option opts [Hash] :tr_attrs html attributes for the <tr> tag
|
378
|
+
# @option opts [Hash] :td_attrs html attributes for the <td> tag
|
379
|
+
# @option opts [Array<Hash>] :special Array of hashes for custom attributes on specific cells (<td> only) of the table
|
380
|
+
# @example Example of the :special attributes
|
381
|
+
# opts[:special] = [
|
382
|
+
# {
|
383
|
+
# col_title: 'rx_DroppedFrameCount', # string or regexp or nil # if neither title nor index are present, the condition is evaluated for all <td> cells
|
384
|
+
# col_index: 5..7, # Fixnum, Range or nil # index has precedence over title
|
385
|
+
# row_title: 'D_0_BE_iMix', # string or regexp or nil
|
386
|
+
# row_index: 6, # Fixnum, Range or nil
|
387
|
+
# condition: Proc.new { |e| Integer(e) != 0 }, # a proc
|
388
|
+
# attributes: {"style" => "background-color: #DB7093;"},
|
389
|
+
# },
|
390
|
+
# ]
|
391
|
+
# @return [REXML::Element] the Element which was just added
|
392
|
+
def custom_table(table_data, opts = {})
|
393
|
+
defaults = {
|
394
|
+
headers: 0,
|
395
|
+
data_is_xhtml: false,
|
396
|
+
table_attrs: {},
|
397
|
+
th_attrs: {},
|
398
|
+
tr_attrs: {},
|
399
|
+
td_attrs: {},
|
400
|
+
special: [],
|
401
|
+
}
|
402
|
+
o = defaults.merge(opts)
|
403
|
+
|
276
404
|
temp = REXML::Element.new("table")
|
277
|
-
temp.add_attributes(table_attrs)
|
405
|
+
temp.add_attributes(o[:table_attrs])
|
406
|
+
row_titles = table_data.collect{|row| row[0].to_s}
|
407
|
+
col_titles = table_data[0].collect{|title| title.to_s}
|
278
408
|
|
279
|
-
for i in 0..table_data.length-1 do
|
280
|
-
row = temp.add_element("tr", tr_attrs)
|
281
|
-
for j in 0..table_data[i].length-1 do
|
282
|
-
if (i == 0 && (0x1 & headers)==0x1)
|
283
|
-
col = row.add_element("th", th_attrs)
|
284
|
-
elsif (j == 0 && (0x2 & headers)==0x2)
|
285
|
-
col = row.add_element("th", th_attrs)
|
286
|
-
elsif ((i == 0 || j ==0) && (0x3 & headers)==0x3)
|
287
|
-
col = row.add_element("th", th_attrs)
|
409
|
+
for i in 0..table_data.length-1 do # row
|
410
|
+
row = temp.add_element("tr", o[:tr_attrs])
|
411
|
+
for j in 0..table_data[i].length-1 do # column
|
412
|
+
if (i == 0 && (0x1 & o[:headers])==0x1)
|
413
|
+
col = row.add_element("th", o[:th_attrs])
|
414
|
+
elsif (j == 0 && (0x2 & o[:headers])==0x2)
|
415
|
+
col = row.add_element("th", o[:th_attrs])
|
416
|
+
elsif ((i == 0 || j ==0) && (0x3 & o[:headers])==0x3)
|
417
|
+
col = row.add_element("th", o[:th_attrs])
|
288
418
|
else
|
289
|
-
|
419
|
+
_td_attrs = o[:td_attrs].clone
|
420
|
+
|
421
|
+
o[:special].each do |h|
|
422
|
+
# check if the current cell is a candidate for special
|
423
|
+
if !h[:col_index].nil?
|
424
|
+
if h[:col_index].is_a?(Range)
|
425
|
+
next if (!h[:col_index].include?(j)) # skip if not in range
|
426
|
+
elsif h[:col_index].is_a?(Integer)
|
427
|
+
next if (h[:col_index] != j) # skip if not at index
|
428
|
+
end
|
429
|
+
elsif !h[:col_title].nil?
|
430
|
+
next if !col_titles[j].match(h[:col_title])
|
431
|
+
end
|
432
|
+
# check if the current cell is a candidate for special
|
433
|
+
if !h[:row_index].nil?
|
434
|
+
if h[:row_index].is_a?(Range)
|
435
|
+
next if (!h[:row_index].include?(i)) # skip if not in range
|
436
|
+
elsif h[:row_index].is_a?(Integer)
|
437
|
+
next if (h[:row_index] != i) # skip if not at index
|
438
|
+
end
|
439
|
+
elsif !h[:row_title].nil?
|
440
|
+
next if !row_titles[i].match(h[:row_title])
|
441
|
+
end
|
442
|
+
|
443
|
+
# here we are a candidate for special, so we check if we meet the condition
|
444
|
+
if h[:condition].call(table_data[i][j])
|
445
|
+
h[:attributes].each { |attr, val|
|
446
|
+
if !_td_attrs[attr].nil?
|
447
|
+
# assume the existing attribute is a string (other types don't make much sense for html)
|
448
|
+
_td_attrs[attr] << val
|
449
|
+
else
|
450
|
+
# create the attribute if it is not already there
|
451
|
+
_td_attrs[attr] = val
|
452
|
+
end
|
453
|
+
}
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
col = row.add_element("td", _td_attrs)
|
458
|
+
end
|
459
|
+
if o[:data_is_xhtml]
|
460
|
+
# we need to create a new document with a pseudo root because having multiple nodes at top
|
461
|
+
# level is not valid xml
|
462
|
+
doc = REXML::Document.new("<root>" + table_data[i][j].to_s + "</root>")
|
463
|
+
# then we move all children of root to the actual div middle element and insert after current
|
464
|
+
for elem in doc.root.to_a do
|
465
|
+
col.add_element(elem) # add the td element
|
466
|
+
end
|
467
|
+
else
|
468
|
+
col.add_text(table_data[i][j].to_s)
|
290
469
|
end
|
291
|
-
col.add_text(table_data[i][j].to_s)
|
292
470
|
end
|
293
471
|
end
|
294
472
|
|
@@ -296,6 +474,7 @@ module Custom
|
|
296
474
|
@current = temp
|
297
475
|
return @current
|
298
476
|
end
|
477
|
+
|
299
478
|
|
300
479
|
# Appends a new heading element to body, and sets current to this new heading
|
301
480
|
# @param tag_type [String] specifiy "h1", "h2", "h3" for the heading, defaults to "h1"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xhtml_report_generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Widmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: "The generator can be used to create html or xhtml files. It comes with
|
14
14
|
many utility functions.\n \nThe javascript to render the table of contents, the
|
@@ -51,10 +51,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
51
|
version: '0'
|
52
52
|
requirements: []
|
53
53
|
rubyforge_project:
|
54
|
-
rubygems_version: 2.
|
54
|
+
rubygems_version: 2.4.5.1
|
55
55
|
signing_key:
|
56
56
|
specification_version: 4
|
57
57
|
summary: A simple html or xhtml generator or logger to create human readable reports
|
58
58
|
and logs
|
59
59
|
test_files: []
|
60
|
-
has_rdoc:
|