xhtml_report_generator 3.1.1 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +1 -1
- data/README.md +32 -21
- data/lib/xhtml_report_generator.rb +702 -48
- data/resource/c3v0.7.18/c3.min.css +1 -0
- data/resource/c3v0.7.18/c3.min.js +2 -0
- data/resource/css/horizontal.png +0 -0
- data/{lib/xhtml_report_generator/print_template.css → resource/css/print.css} +0 -0
- data/{lib/xhtml_report_generator/style_template.css → resource/css/style.css} +0 -0
- data/resource/css/vertical.png +0 -0
- data/resource/d3v5.16.0/d3.min.js +2 -0
- data/resource/js/jquery-3.5.1.min.js +2 -0
- data/resource/js/layout_split.js +37 -0
- data/{lib/xhtml_report_generator → resource/js}/split.min.js +0 -0
- data/resource/js/table_of_contents.js +169 -0
- data/resource/js/toggle_linewrap.js +15 -0
- data/resource/passfail_bgcolor.js +16 -0
- data/{lib/xhtml_report_generator → resource}/toc.min.js +0 -0
- data/resource/toc_full.js +230 -0
- metadata +26 -17
- data/lib/xhtml_report_generator/custom.rb +0 -597
- data/lib/xhtml_report_generator/jquery.min.js +0 -5
- data/lib/xhtml_report_generator/version.rb +0 -5
metadata
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xhtml_report_generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Widmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
description: |
|
14
|
+
The generator can be used to create html or xhtml files. It comes with many utility functions.
|
15
|
+
|
16
|
+
The javascript to render the table of contents, the custom generator functions and style sheet all can be
|
17
|
+
supplied by your own, if necessary. By default there are methods to insert tables, links, paragraphs, preformatted text
|
18
|
+
and arbitrary xhtml code. Due to the xml nature it is also easy to insert SVG graphs / pictures.
|
19
|
+
|
20
|
+
Checkout the github project to see some examples.
|
19
21
|
email: m-widmer@gmx.ch
|
20
22
|
executables: []
|
21
23
|
extensions: []
|
@@ -24,13 +26,21 @@ files:
|
|
24
26
|
- LICENSE
|
25
27
|
- README.md
|
26
28
|
- lib/xhtml_report_generator.rb
|
27
|
-
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
-
|
32
|
-
-
|
33
|
-
-
|
29
|
+
- resource/c3v0.7.18/c3.min.css
|
30
|
+
- resource/c3v0.7.18/c3.min.js
|
31
|
+
- resource/css/horizontal.png
|
32
|
+
- resource/css/print.css
|
33
|
+
- resource/css/style.css
|
34
|
+
- resource/css/vertical.png
|
35
|
+
- resource/d3v5.16.0/d3.min.js
|
36
|
+
- resource/js/jquery-3.5.1.min.js
|
37
|
+
- resource/js/layout_split.js
|
38
|
+
- resource/js/split.min.js
|
39
|
+
- resource/js/table_of_contents.js
|
40
|
+
- resource/js/toggle_linewrap.js
|
41
|
+
- resource/passfail_bgcolor.js
|
42
|
+
- resource/toc.min.js
|
43
|
+
- resource/toc_full.js
|
34
44
|
homepage: https://rubygems.org/gems/xhtml_report_generator
|
35
45
|
licenses:
|
36
46
|
- MIT
|
@@ -50,8 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
60
|
- !ruby/object:Gem::Version
|
51
61
|
version: '0'
|
52
62
|
requirements: []
|
53
|
-
|
54
|
-
rubygems_version: 2.4.5.1
|
63
|
+
rubygems_version: 3.1.2
|
55
64
|
signing_key:
|
56
65
|
specification_version: 4
|
57
66
|
summary: A simple html or xhtml generator or logger to create human readable reports
|
@@ -1,597 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'base64'
|
3
|
-
# The module name doesn't matter, just make sure at the end to 'extend' it
|
4
|
-
# because it will be 'eval'ed by the initialize method of the XhtmlReportGenerator::Generator class.
|
5
|
-
module Custom
|
6
|
-
|
7
|
-
# creates the basic page layout and sets the current Element to the main content area (middle div)
|
8
|
-
# @example The middle div is matched by the following xPath
|
9
|
-
# //body/div[@id='middle']
|
10
|
-
# @param title [String] the title of the document
|
11
|
-
# @param layout [Fixnum] one of 0,1,2,3 where 0 means minimal layout without left and right table of contents,
|
12
|
-
# 1 means only left toc, 2 means only right toc, and 3 means full layout with left and right toc.
|
13
|
-
def create_layout(title, layout=3)
|
14
|
-
raise "invalid layout selector, choose from 0..3" if (layout < 0) || (layout > 3)
|
15
|
-
|
16
|
-
@body = @document.elements["//body"]
|
17
|
-
# only add the layout if it is not already there
|
18
|
-
if !@layout
|
19
|
-
head = @body.add_element("div", {"class" => "head", "id" => "head"})
|
20
|
-
head.add_element("button", {"id" => "pre_toggle_linewrap"}).add_text("Toggle Linewrap")
|
21
|
-
|
22
|
-
if (layout & 0x1) != 0
|
23
|
-
div = @body.add_element("div", {"class" => "lefttoc split split-horizontal", "id" => "ltoc"})
|
24
|
-
div.add_text("Table of Contents")
|
25
|
-
div.add_element("br")
|
26
|
-
end
|
27
|
-
|
28
|
-
@div_middle = @body.add_element("div", {"class" => "middle split split-horizontal", "id" => "middle"})
|
29
|
-
|
30
|
-
if (layout & 0x2) != 0
|
31
|
-
div = @body.add_element("div", {"class" => "righttoc split split-horizontal", "id" => "rtoc"})
|
32
|
-
div.add_text("Quick Links")
|
33
|
-
div.add_element("br");div.add_element("br")
|
34
|
-
end
|
35
|
-
|
36
|
-
@body.add_element("p", {"class" => "#{layout}", "id" => "layout"}).add_text("this text should be hidden")
|
37
|
-
|
38
|
-
@layout = true
|
39
|
-
end
|
40
|
-
@current = @document.elements["//body/div[@id='middle']"]
|
41
|
-
set_title(title)
|
42
|
-
end
|
43
|
-
|
44
|
-
# sets the title of the document in the <head> section as well as in the layout header div
|
45
|
-
# create_layout must be called before!
|
46
|
-
# @param title [String] the text which will be insertead
|
47
|
-
def set_title(title)
|
48
|
-
if !@layout
|
49
|
-
raise "call create_layout first"
|
50
|
-
end
|
51
|
-
pagetitle = @document.elements["//head/title"]
|
52
|
-
pagetitle.text = title
|
53
|
-
div = @document.elements["//body/div[@id='head']"]
|
54
|
-
div.text = title
|
55
|
-
end
|
56
|
-
|
57
|
-
# returns the title text of the report
|
58
|
-
# @return [String] The title of the report
|
59
|
-
def get_title()
|
60
|
-
pagetitle = @document.elements["//head/title"]
|
61
|
-
return pagetitle.text
|
62
|
-
end
|
63
|
-
|
64
|
-
# set the current element to the element or first element matched by the xpath expression.
|
65
|
-
# The current element is the one which can be modified through highlighting.
|
66
|
-
# @param xpath [REXML::Element|String] the element or an xpath string
|
67
|
-
def set_current!(xpath)
|
68
|
-
if xpath.is_a?(REXML::Element)
|
69
|
-
@current = xpath
|
70
|
-
elsif xpath.is_a?(String)
|
71
|
-
@current = @document.elements[xpath]
|
72
|
-
else
|
73
|
-
raise "xpath is neither a String nor a REXML::Element"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# returns the current xml element
|
78
|
-
# @return [REXML::Element] the xml element after which the following elements will be added
|
79
|
-
def get_current()
|
80
|
-
return @current
|
81
|
-
end
|
82
|
-
|
83
|
-
# returns the plain text without any xml tags of the specified element and all its children
|
84
|
-
# @param el [REXML::Element] The element from which to fetch the text children. Defaults to @current
|
85
|
-
# @param recursive [Boolean] whether or not to recurse into the children of the given "el"
|
86
|
-
# @return [String] text contents of xml node
|
87
|
-
def get_element_text(el = @current, recursive = true)
|
88
|
-
out = ""
|
89
|
-
el.to_a.each { |child|
|
90
|
-
if child.is_a?(REXML::Text)
|
91
|
-
out << child.value()
|
92
|
-
else
|
93
|
-
if recursive
|
94
|
-
out << get_element_text(child, true)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
}
|
98
|
-
return out
|
99
|
-
end
|
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
|
-
|
123
|
-
# Appends a <pre> node after the @current node
|
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;"}.
|
128
|
-
# @yieldreturn [String] the text to be added to the <pre> element
|
129
|
-
# @return [REXML::Element] the Element which was just added
|
130
|
-
def code(attrs={}, &block)
|
131
|
-
temp = REXML::Element.new("pre")
|
132
|
-
temp.add_attributes(attrs)
|
133
|
-
@div_middle.insert_after(@current, temp)
|
134
|
-
@current = temp
|
135
|
-
raise "Block argument is mandatory" unless block_given?
|
136
|
-
text = encoding_fixer(block.call())
|
137
|
-
@current.add_text(text)
|
138
|
-
return @current
|
139
|
-
end
|
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
|
-
|
154
|
-
# Appends a <p> node after the @current node
|
155
|
-
# @param attrs [Hash] attributes for the <p> element
|
156
|
-
# @yieldreturn [String] the text to be added to the <p> element
|
157
|
-
# @return [REXML::Element] the Element which was just added
|
158
|
-
def content(attrs={}, &block)
|
159
|
-
temp = REXML::Element.new("p")
|
160
|
-
temp.add_attributes(attrs)
|
161
|
-
@div_middle.insert_after(@current, temp)
|
162
|
-
@current = temp
|
163
|
-
raise "Block argument is mandatory" unless block_given?
|
164
|
-
text = encoding_fixer(block.call())
|
165
|
-
@current.add_text(text)
|
166
|
-
return @current
|
167
|
-
end
|
168
|
-
|
169
|
-
# insert arbitrary xml code after the @current element in the content pane (div middle)
|
170
|
-
# @param text [String] valid xhtml code which is included into the document
|
171
|
-
# @return [REXML::Element] the Element which was just added
|
172
|
-
def html(text)
|
173
|
-
# we need to create a new document with a pseudo root becaus having multiple nodes at top
|
174
|
-
# level is not valid xml
|
175
|
-
doc = REXML::Document.new("<root>"+text+"</root>")
|
176
|
-
# then we move all children of root to the actual div middle element and insert after current
|
177
|
-
for i in doc.root.to_a do
|
178
|
-
@div_middle.insert_after(@current, i)
|
179
|
-
@current = i
|
180
|
-
end
|
181
|
-
return @current
|
182
|
-
end
|
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
|
-
|
198
|
-
# Appends a <a href = > node after the @current nodes
|
199
|
-
# @param href [String] this is the
|
200
|
-
# @param attrs [Hash] attributes for the <a> element
|
201
|
-
# @yieldreturn [String] the text to be added to the <a> element
|
202
|
-
# @return [REXML::Element] the Element which was just added
|
203
|
-
def link(href, attrs={}, &block)
|
204
|
-
temp = REXML::Element.new("a")
|
205
|
-
attrs.merge!({"href" => href})
|
206
|
-
temp.add_attributes(attrs)
|
207
|
-
@div_middle.insert_after(@current, temp)
|
208
|
-
@current = temp
|
209
|
-
raise "Block argument is mandatory" unless block_given?
|
210
|
-
text = encoding_fixer(block.call())
|
211
|
-
@current.add_text(text)
|
212
|
-
return @current
|
213
|
-
end
|
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
|
-
|
231
|
-
# @param path [String] absolute or relative path to the image that should be inserted into the report
|
232
|
-
# @param attributes [Hash] attributes for the <img> element, any valid html attributes can be specified
|
233
|
-
# you may specify attributes such "alt", "height", "width"
|
234
|
-
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
235
|
-
def image(path, attributes = {})
|
236
|
-
# read image as binary and do a base64 encoding
|
237
|
-
binary_data = Base64.strict_encode64(IO.binread(path))
|
238
|
-
type = File.extname(path).gsub('.', '')
|
239
|
-
# create the element
|
240
|
-
temp = REXML::Element.new("img")
|
241
|
-
# add the picture
|
242
|
-
temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
|
243
|
-
temp.add_attributes(attributes)
|
244
|
-
|
245
|
-
@div_middle.insert_after(@current, temp)
|
246
|
-
@current = temp
|
247
|
-
return @current
|
248
|
-
end
|
249
|
-
|
250
|
-
# Scans all REXML::Text children of an REXML::Element for any occurrences of regex.
|
251
|
-
# The text will be matched as one, not line by line as you might think.
|
252
|
-
# If you want to write a regexp matching multiple lines keep in mind that the dot "." by default doesn't
|
253
|
-
# match newline characters. Consider using the "m" option (e.g. /regex/m ) which makes dot match newlines
|
254
|
-
# or match newlines explicitly.
|
255
|
-
# highlight_captures then puts a <span> </span> tag around all captures of the regex
|
256
|
-
# NOTE: nested captures are not supported and don't make sense in this context!!
|
257
|
-
# @param regex [Regexp] a regular expression that will be matched
|
258
|
-
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
259
|
-
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches, by default the last inserted element will be scanned
|
260
|
-
# @return [Fixnum] the number of highlighted captures
|
261
|
-
def highlight_captures(regex, color="y", el = @current)
|
262
|
-
# get all children of the current node
|
263
|
-
arr = el.to_a()
|
264
|
-
num_matches = 0
|
265
|
-
# first we have to detach all children from parent, otherwise we can cause ordering issues
|
266
|
-
arr.each {|i| i.remove() }
|
267
|
-
# depth first recursion into grand-children
|
268
|
-
for i in arr do
|
269
|
-
if i.is_a?(REXML::Text)
|
270
|
-
# in general a text looks as follows:
|
271
|
-
# .*(matchstring|.*)*
|
272
|
-
|
273
|
-
# We get an array of [[start,length], [start,length], ...] for all our regex SUB-matches
|
274
|
-
positions = i.value().enum_for(:scan, regex).flat_map {
|
275
|
-
# Regexp.last_match is a MatchData object, the index 0 is the entire match, 1 to n are captures
|
276
|
-
array = Array.new
|
277
|
-
for k in 1..Regexp.last_match.length - 1 do
|
278
|
-
array.push([Regexp.last_match.begin(k),
|
279
|
-
Regexp.last_match.end(k)-Regexp.last_match.begin(k)])
|
280
|
-
end
|
281
|
-
# return the array for the flat_map
|
282
|
-
array
|
283
|
-
}
|
284
|
-
num_matches += positions.length
|
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)
|
293
|
-
else
|
294
|
-
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
295
|
-
num_matches += highlight_captures(regex, color, i)
|
296
|
-
el.add(i)
|
297
|
-
end # if i.is_a?(REXML::Text)
|
298
|
-
end # for i in arr do
|
299
|
-
return num_matches
|
300
|
-
end
|
301
|
-
|
302
|
-
# Scans all REXML::Text children of an REXML::Element for any occurrences of regex.
|
303
|
-
# The text will be matched as one, not line by line as you might think.
|
304
|
-
# If you want to write a regexp matching multiple lines keep in mind that the dot "." by default doesn't
|
305
|
-
# match newline characters. Consider using the "m" option (e.g. /regex/m ) which makes dot match newlines
|
306
|
-
# or match newlines explicitly.
|
307
|
-
# highlight then puts a <span> </span> tag around all matches of regex
|
308
|
-
# @param regex [Regexp] a regular expression that will be matched
|
309
|
-
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
310
|
-
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches
|
311
|
-
# @return [Fixnum] the number of highlighted captures
|
312
|
-
def highlight(regex, color="y", el = @current)
|
313
|
-
# get all children of the current node
|
314
|
-
arr = el.to_a()
|
315
|
-
num_matches = 0
|
316
|
-
#puts arr.inspect
|
317
|
-
# first we have to detach all children from parent, otherwise we can cause ordering issues
|
318
|
-
arr.each {|i| i.remove() }
|
319
|
-
# depth first recursion into grand-children
|
320
|
-
for i in arr do
|
321
|
-
#puts i.class.to_s()
|
322
|
-
if i.is_a?(REXML::Text)
|
323
|
-
# in general a text looks as follows:
|
324
|
-
# .*(matchstring|.*)*
|
325
|
-
|
326
|
-
# We get an array of [[start,length], [start,length], ...] for all our regex matches
|
327
|
-
positions = i.value().enum_for(:scan, regex).map {
|
328
|
-
[Regexp.last_match.begin(0),
|
329
|
-
Regexp.last_match.end(0)-Regexp.last_match.begin(0)]
|
330
|
-
}
|
331
|
-
num_matches += positions.length
|
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)
|
340
|
-
else
|
341
|
-
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
342
|
-
# puts "recurse"
|
343
|
-
num_matches += highlight(regex, color, i)
|
344
|
-
el.add(i)
|
345
|
-
end # if i.is_a?(REXML::Text)
|
346
|
-
end # for i in arr do
|
347
|
-
return num_matches
|
348
|
-
end
|
349
|
-
|
350
|
-
# creates a html table from two dimensional array of the form Array [row] [col]
|
351
|
-
# @param table_data [Array<Array>] of the form Array [row] [col] containing all data, the '.to_s' method will be called on each element,
|
352
|
-
# @param headers [Number] either of 0, 1, 2, 3. Where 0 is no headers (<th>) at all, 1 is only the first row,
|
353
|
-
# 2 is only the first column and 3 is both, first row and first column as <th> elements. Every other number
|
354
|
-
# is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3
|
355
|
-
# @return [REXML::Element] the Element which was just added
|
356
|
-
def table(table_data, headers=0, table_attrs={}, tr_attrs={}, th_attrs={}, td_attrs={})
|
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
|
-
|
404
|
-
temp = REXML::Element.new("table")
|
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}
|
408
|
-
|
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])
|
418
|
-
else
|
419
|
-
# we need to deepcopy the attributes
|
420
|
-
_td_attrs = Marshal.load(Marshal.dump(o[:td_attrs]))
|
421
|
-
|
422
|
-
# check all special criteria
|
423
|
-
o[:special].each do |h|
|
424
|
-
# check if the current cell is a candidate for special
|
425
|
-
if !h[:col_index].nil?
|
426
|
-
if h[:col_index].is_a?(Range)
|
427
|
-
next if (!h[:col_index].include?(j)) # skip if not in range
|
428
|
-
elsif h[:col_index].is_a?(Integer)
|
429
|
-
next if (h[:col_index] != j) # skip if not at index
|
430
|
-
end
|
431
|
-
elsif !h[:col_title].nil?
|
432
|
-
next if !col_titles[j].match(h[:col_title])
|
433
|
-
end
|
434
|
-
# check if the current cell is a candidate for special
|
435
|
-
if !h[:row_index].nil?
|
436
|
-
if h[:row_index].is_a?(Range)
|
437
|
-
next if (!h[:row_index].include?(i)) # skip if not in range
|
438
|
-
elsif h[:row_index].is_a?(Integer)
|
439
|
-
next if (h[:row_index] != i) # skip if not at index
|
440
|
-
end
|
441
|
-
elsif !h[:row_title].nil?
|
442
|
-
next if !row_titles[i].match(h[:row_title])
|
443
|
-
end
|
444
|
-
|
445
|
-
# here we are a candidate for special, so we check if we meet the condition
|
446
|
-
# puts h[:attributes].inspect
|
447
|
-
# puts "cell value row #{i} col #{j}: #{table_data[i][j]}"
|
448
|
-
# puts h[:condition].call(table_data[i][j]).inspect
|
449
|
-
if h[:condition].call(table_data[i][j])
|
450
|
-
h[:attributes].each { |attr, val|
|
451
|
-
# debug, verify deepcopy
|
452
|
-
# puts "objects are equal: #{_td_attrs[attr].equal?(o[:td_attrs][attr])}"
|
453
|
-
if !_td_attrs[attr].nil?
|
454
|
-
# assume the existing attribute is a string (other types don't make much sense for html)
|
455
|
-
_td_attrs[attr] << val
|
456
|
-
else
|
457
|
-
# create the attribute if it is not already there
|
458
|
-
_td_attrs[attr] = val
|
459
|
-
end
|
460
|
-
}
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
col = row.add_element("td", _td_attrs)
|
465
|
-
end
|
466
|
-
if o[:data_is_xhtml]
|
467
|
-
# we need to create a new document with a pseudo root because having multiple nodes at top
|
468
|
-
# level is not valid xml
|
469
|
-
doc = REXML::Document.new("<root>" + table_data[i][j].to_s + "</root>")
|
470
|
-
# then we move all children of root to the actual div middle element and insert after current
|
471
|
-
for elem in doc.root.to_a do
|
472
|
-
col.add_element(elem) # add the td element
|
473
|
-
end
|
474
|
-
else
|
475
|
-
col.add_text(table_data[i][j].to_s)
|
476
|
-
end
|
477
|
-
end
|
478
|
-
end
|
479
|
-
|
480
|
-
@div_middle.insert_after(@current, temp)
|
481
|
-
@current = temp
|
482
|
-
return @current
|
483
|
-
end
|
484
|
-
|
485
|
-
|
486
|
-
# Appends a new heading element to body, and sets current to this new heading
|
487
|
-
# @param tag_type [String] specifiy "h1", "h2", "h3" for the heading, defaults to "h1"
|
488
|
-
# @param attrs [Hash] attributes for the <h#> element, any valid html attributes can be specified
|
489
|
-
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
490
|
-
# use the class "onlyrtoc" or "bothtoc" to add a heading only to the right toc or to both tocs respectively
|
491
|
-
# @yieldreturn [String] the text to be added to the <h#> element
|
492
|
-
# @return [REXML::Element] the Element which was just added
|
493
|
-
def heading(tag_type="h1", attrs={}, &block)
|
494
|
-
temp = REXML::Element.new(tag_type)
|
495
|
-
temp.add_attributes(attrs)
|
496
|
-
|
497
|
-
@div_middle.insert_after(@current, temp)
|
498
|
-
@current = temp
|
499
|
-
raise "Block argument is mandatory" unless block_given?
|
500
|
-
text = encoding_fixer(block.call())
|
501
|
-
@current.text = text
|
502
|
-
return @current
|
503
|
-
end
|
504
|
-
|
505
|
-
# Inserts a new heading element at the very beginning of the middle div section, and points @current to this heading
|
506
|
-
# @param tag_type [String] specifiy "h1", "h2", "h3" for the heading, defaults to "h1"
|
507
|
-
# @param attrs [Hash] attributes for the <h#> element, any valid html attributes can be specified
|
508
|
-
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
509
|
-
# use the class "onlyrtoc" or "bothtoc" to add a heading only to the right toc or to both tocs respectively
|
510
|
-
# @yieldreturn [String] the text to be added to the <h#> element
|
511
|
-
# @return [REXML::Element] the Element which was just added
|
512
|
-
def heading_top(tag_type="h1", attrs={}, &block)
|
513
|
-
temp = REXML::Element.new(tag_type)
|
514
|
-
temp.add_attributes(attrs)
|
515
|
-
|
516
|
-
# check if there are any child elements
|
517
|
-
if @div_middle.has_elements?()
|
518
|
-
# insert before the first child of div middle
|
519
|
-
@div_middle.insert_before("//div[@id='middle']/*[1]", temp)
|
520
|
-
else
|
521
|
-
# middle is empty, just insert the heading
|
522
|
-
@div_middle.insert_after(@current, temp)
|
523
|
-
end
|
524
|
-
|
525
|
-
@current = temp
|
526
|
-
raise "Block argument is mandatory" unless block_given?
|
527
|
-
text = encoding_fixer(block.call())
|
528
|
-
@current.text = text
|
529
|
-
return @current
|
530
|
-
end
|
531
|
-
|
532
|
-
# Helper Method for the highlight methods. it will introduce specific xhtml tags around parts of a text child of an xml element.
|
533
|
-
# @example
|
534
|
-
# we have the following xml part
|
535
|
-
# <test>
|
536
|
-
# some arbitrary
|
537
|
-
# text child content
|
538
|
-
# </test>
|
539
|
-
# now we call replace_text_with_elements to place <span> around the word "arbitrary"
|
540
|
-
# =>
|
541
|
-
# <test>
|
542
|
-
# some <span>arbitrary</span>
|
543
|
-
# text child content
|
544
|
-
# </test>
|
545
|
-
# @param parent [REXML::Element] the parent to which "element" should be attached after parsing, e.g. <test>
|
546
|
-
# @param element [REXML::Element] the Text element, into which tags will be added at the specified indices of @index_length_array, e.g. the REXML::Text children of <test> in the example
|
547
|
-
# @param tagname [String] the tag that will be introduced as <tagname> at the indices specified
|
548
|
-
# @param attribs [Hash] Attributes that will be added to the inserted tag e.g. <tagname attrib="test">
|
549
|
-
# @param index_length_array [Array] Array of the form [[index, lenght], [index, lenght], ...] that specifies
|
550
|
-
# the start position and length of the substring around which the tags will be introduced
|
551
|
-
def replace_text_with_elements(parent, element, tagname, attribs, index_length_array)
|
552
|
-
last_end = 0
|
553
|
-
index = 0
|
554
|
-
#puts index_length_array.inspect
|
555
|
-
#puts element.inspect
|
556
|
-
for j in index_length_array do
|
557
|
-
# reattach normal (unmatched) text
|
558
|
-
if j[0] > last_end
|
559
|
-
# text = REXML::Text.new(element.value()[ last_end, j[0] - last_end ])
|
560
|
-
# parent.add_text(text)
|
561
|
-
# add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
|
562
|
-
parent.add_text( element.value()[ last_end, j[0] - last_end ] )
|
563
|
-
end
|
564
|
-
#create the tag node with attributes and add the text to it
|
565
|
-
tag = parent.add_element(REXML::Element.new(tagname), attribs)
|
566
|
-
tag.add_text(element.value()[ j[0], j[1] ])
|
567
|
-
last_end = j[0]+j[1]
|
568
|
-
|
569
|
-
# in the last round check for any remaining text
|
570
|
-
if index == index_length_array.length - 1
|
571
|
-
if last_end < element.value().length
|
572
|
-
# text = REXML::Text.new(element.value()[ last_end, element.value().length - last_end ])
|
573
|
-
# parent.add(text)
|
574
|
-
# add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
|
575
|
-
parent.add_text( element.value()[ last_end, element.value().length - last_end ] )
|
576
|
-
end
|
577
|
-
end
|
578
|
-
index += 1
|
579
|
-
end # for j in positions do
|
580
|
-
|
581
|
-
# don't forget to reattach the textnode if there are no regex matches at all
|
582
|
-
if index == 0
|
583
|
-
parent.add(element)
|
584
|
-
end
|
585
|
-
|
586
|
-
end
|
587
|
-
|
588
|
-
#private_instance_methods(:replace_text_with_elements)
|
589
|
-
|
590
|
-
end
|
591
|
-
|
592
|
-
extend Custom
|
593
|
-
#class Test
|
594
|
-
# include XhtmlReportGenerator::Custom
|
595
|
-
#
|
596
|
-
#end
|
597
|
-
#puts Test.new.header()
|