xhtml_report_generator 3.1.1 → 4.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 +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()
|