xhtml_report_generator 3.1.2 → 4.0.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 +26 -19
- data/lib/xhtml_report_generator.rb +694 -45
- data/resource/c3v0.4.18/LICENSE +20 -0
- data/resource/c3v0.4.18/c3.min.css +1 -0
- data/resource/c3v0.4.18/c3.min.js +1 -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/d3v3.5.17/LICENSE +26 -0
- data/resource/d3v3.5.17/d3.js +9554 -0
- data/resource/d3v3.5.17/d3.min.js +5 -0
- data/resource/js/jquery-3.2.1.min.js +4 -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
- metadata +24 -16
- data/lib/xhtml_report_generator/custom.rb +0 -602
- data/lib/xhtml_report_generator/jquery.min.js +0 -5
- data/lib/xhtml_report_generator/toc.min.js +0 -6
- data/lib/xhtml_report_generator/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca9e62e4f9ddf0bbc7c52be1956bffe5ccd46b62
|
4
|
+
data.tar.gz: ebe93934511f888827cf8a83a3c8ecb80449327b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8ee85b1bdc6677051f69288b8530d8c43d55dd93e41970ae7e3748f063efd647287952c5ece3387983a99aa88f472455cc6c98bce3f214ae0254ffbaf5f3f54
|
7
|
+
data.tar.gz: b2750549a9a8e5d44ca6e4cdceddfb979e28dcc71d5c0cd66bdcc7882741cabd263f8ea1209dc225841bf97763428e91e8d1010990d85c7b7bf7c91d310eb3d9
|
data/README.md
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
xhtml_report_generator
|
2
2
|
======================
|
3
3
|
|
4
|
-
This project was written to provide an easy way to create valid xhtml or
|
5
|
-
|
4
|
+
This project was written to provide an easy way to create valid xhtml or html5 documents.
|
5
|
+
The main use cases is the automatic creation of (test-)reports that are human readable and include a table of contents.
|
6
6
|
xhtml_report_generator can be used very similar like a ruby Logger, but there are some caveats.
|
7
7
|
It is not a Logger replacement, since the complete document is always kept in memory and
|
8
8
|
only written to disk on demand. Hence in case of crashes the data might be lost if it wasn't written before.
|
9
|
+
There is a "sync" option but it has a performance penalty if you need to generate a lot of content.
|
9
10
|
|
10
|
-
All logic (js and css) is inlined which makes it very easy to send the report
|
11
|
+
All logic (js and css) is inlined which makes it very easy to send the report by mail and view it offline.
|
11
12
|
Also pdf export is easy by just printing the report. By default there is a special css with media print making the layout suitable for printing.
|
12
13
|
|
13
14
|
Ruby version
|
14
15
|
-----
|
15
|
-
This gem was mainly tested with ruby
|
16
|
+
This gem was mainly tested with ruby versions >=2.2. Except of the test_encoding_issues unit tests, all other tests are
|
16
17
|
also passing with 1.9.3.
|
17
18
|
|
18
19
|
|
@@ -43,14 +44,11 @@ gen1.write("myreport.xhtml")
|
|
43
44
|
|
44
45
|
[Preview](https://cdn.rawgit.com/lumean/xhtml-report-generator/master/examples/basic_report.html)
|
45
46
|
|
47
|
+
More examples can be found in the [examples](../master/examples) or [test](../master/test) folders
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
By default "custom.rb" is loaded through instance eval, see
|
52
|
-
[XhtmlReportGenerator/Custom](http://www.rubydoc.info/gems/xhtml_report_generator/Custom) and
|
53
|
-
[XhtmlReportGenerator/Generator](http://www.rubydoc.info/gems/xhtml_report_generator/XhtmlReportGenerator/Generator)
|
49
|
+
Documentation
|
50
|
+
-----
|
51
|
+
See [XhtmlReportGenerator/Generator](http://www.rubydoc.info/gems/xhtml_report_generator/XhtmlReportGenerator/Generator)
|
54
52
|
for the documentation of available methods.
|
55
53
|
|
56
54
|
Advanced example1: custom tables including pictures or links
|
@@ -119,9 +117,10 @@ gen1.write("path/to/CustomTable.xhtml")
|
|
119
117
|
[Preview](https://cdn.rawgit.com/lumean/xhtml-report-generator/master/test/CustomTableReference.xhtml)
|
120
118
|
|
121
119
|
|
122
|
-
Advanced example2: including some graphs to your reports
|
120
|
+
Advanced example2: including some graphs/charts to your reports
|
123
121
|
----------------------------------
|
124
|
-
Due to the xml nature it is also easy to insert SVG graphs / pictures. Check out the svg-graph gem
|
122
|
+
Due to the xml nature it is also easy to insert SVG graphs / pictures. Check out the svg-graph gem,
|
123
|
+
or you can even natively include a c3.js graph
|
125
124
|
|
126
125
|
```ruby
|
127
126
|
require 'xhtml_report_generator'
|
@@ -164,10 +163,10 @@ gen1.write("graph.xhtml")
|
|
164
163
|
|
165
164
|
Customizing the Report with CSS
|
166
165
|
-------------------------------
|
167
|
-
The styling of the report is done through css. This
|
166
|
+
The styling of the report is done through css. This allows you to customize most of the formatting as to your liking.
|
168
167
|
The split.js relevant section should only be changed if you know what you're doing, otherwise the layout might break.
|
169
168
|
|
170
|
-
As a starting point begin with the [default css used by the report](../master/
|
169
|
+
As a starting point begin with the [default css used by the report](../master/resource/css/style.css)
|
171
170
|
```ruby
|
172
171
|
require 'xhtml_report_generator'
|
173
172
|
|
@@ -185,12 +184,20 @@ gen1.create_layout("Page Title")
|
|
185
184
|
[Preview](https://cdn.rawgit.com/lumean/xhtml-report-generator/master/examples/custom_css.html)
|
186
185
|
|
187
186
|
The project is built in a way that lets you supply your own methods for everything. By default the methods , js and css files provided
|
188
|
-
with the gem are used, but you can override those by specifying your own. The primary
|
189
|
-
to customize the look and feel of the generated html files. But if you want you can
|
187
|
+
with the gem are used, but you can override those by specifying your own. The primary use case is to override the default css
|
188
|
+
to customize the look and feel of the generated html files. But if you want you can even write your own generator.
|
189
|
+
Have a look at [custom_reporter.rb](../master/lib/test/custom_reporter.rb).
|
190
|
+
|
191
|
+
Changes from version 3.x to 4.x
|
192
|
+
-------------------------------
|
193
|
+
If you just use the default values for initialize (i.e. no options/using defaults) then the upgrade should be seamless.
|
190
194
|
|
191
|
-
|
192
|
-
|
195
|
+
The option :custom_rb was removed and behavior for the initialize method "XhtmlReportGenerator::Generator.new" changed.
|
196
|
+
You should extend your own subclass from XhtmlReportGenerator::Generator to do any customization.
|
197
|
+
The js, css and css_print files given for initialize are now included after the default files. Previously if you'd
|
198
|
+
specify any of those files, only your files would have been included in the head section.
|
193
199
|
|
200
|
+
For a complete list of changes see [changelog.txt](../master/changelog.txt)
|
194
201
|
|
195
202
|
Changes from version 2.x to 3.x
|
196
203
|
-------------------------------
|
@@ -1,81 +1,103 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'rexml/document'
|
3
3
|
require 'rexml/formatters/transitive'
|
4
|
+
require 'base64'
|
4
5
|
|
5
6
|
module XhtmlReportGenerator
|
6
|
-
|
7
|
+
|
8
|
+
VERSION = '4.0.0'
|
9
|
+
|
7
10
|
# This is the main generator class. It can be instanced with custom javascript, css, and ruby files to allow
|
8
11
|
# generation of arbitrary reports.
|
12
|
+
# @attr [REXML::Document] document This is the html document / actual report
|
13
|
+
# @attr [String] file path to the file where this report is saved to. Default: nil
|
14
|
+
# @attr [Boolean] sync if true, the report will be written to disk after every modificaiton. Default: false
|
9
15
|
class Generator
|
10
|
-
attr_accessor :document, :file
|
16
|
+
attr_accessor :document, :file, :sync
|
11
17
|
# @param opts [Hash] See the example for an explanation of the valid symbols
|
12
|
-
# @example Valid
|
13
|
-
# :
|
14
|
-
# :
|
18
|
+
# @example Valid keys for the opts Hash
|
19
|
+
# :title Title in the header section, defaults to "Title"
|
20
|
+
# :js if specified, array of javascript files which are inlined into the html header section, after
|
21
|
+
# the default included js files (check in sourcecode below).
|
22
|
+
# :css if specified, array of css files which are inlined into the html header section after
|
23
|
+
# the default included css files (check in sourcecode below).
|
15
24
|
# :css_print if specified, array of css files which are inlined into the html header section with media=print
|
16
|
-
#
|
17
|
-
# see (custom.rb) on how to write it. As a last statement you should extend your module name
|
18
|
-
# outside of the module definition.
|
25
|
+
# after the default included print css files (check in sourcecode below).
|
19
26
|
def initialize(opts = {})
|
20
27
|
# define the default values
|
21
|
-
|
28
|
+
resources = File.expand_path("../../resource/", __FILE__)
|
22
29
|
defaults = {
|
30
|
+
:title => "Title",
|
23
31
|
:js => [
|
24
|
-
File.expand_path("jquery.min.js",
|
25
|
-
File.expand_path("
|
26
|
-
File.expand_path("
|
32
|
+
File.expand_path("js/jquery-3.2.1.min.js", resources),
|
33
|
+
File.expand_path("d3v3.5.17/d3.min.js", resources),
|
34
|
+
File.expand_path("c3v0.4.18/c3.min.js", resources),
|
35
|
+
File.expand_path("js/split.min.js", resources),
|
36
|
+
File.expand_path("js/layout_split.js", resources),
|
37
|
+
File.expand_path("js/table_of_contents.js", resources),
|
38
|
+
File.expand_path("js/toggle_linewrap.js", resources),
|
27
39
|
],
|
28
40
|
:css => [
|
29
|
-
File.expand_path("
|
41
|
+
File.expand_path("css/style.css", resources),
|
42
|
+
File.expand_path("c3v0.4.18/c3.min.css", resources),
|
30
43
|
],
|
31
44
|
:css_print => [
|
32
|
-
File.expand_path("
|
45
|
+
File.expand_path("css/print.css", resources)
|
33
46
|
],
|
34
|
-
|
47
|
+
#:custom_rb => File.expand_path("../custom.rb", __FILE__),
|
35
48
|
}
|
36
|
-
|
37
|
-
|
38
|
-
opts[:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
@sync = false
|
50
|
+
|
51
|
+
opts[:title] = defaults[:title] if !opts.key?(:title)
|
52
|
+
|
53
|
+
if opts.key?(:js)
|
54
|
+
opts[:js] = defaults[:js] + opts[:js]
|
55
|
+
else
|
56
|
+
opts[:js] = defaults[:js]
|
57
|
+
end
|
58
|
+
if opts.key?(:css)
|
59
|
+
opts[:css] = defaults[:css] + opts[:css]
|
60
|
+
else
|
61
|
+
opts[:css] = defaults[:css]
|
62
|
+
end
|
63
|
+
if opts.key?(:css_print)
|
64
|
+
opts[:css_print] = defaults[:css_print] + opts[:css_print]
|
65
|
+
else
|
66
|
+
opts[:css_print] = defaults[:css_print]
|
67
|
+
end
|
68
|
+
|
69
|
+
@document = Generator.create_xhtml_document(opts[:title])
|
47
70
|
head = @document.elements["//head"]
|
48
|
-
|
71
|
+
|
49
72
|
head.add_element("meta", {"charset" => "utf-8"})
|
50
|
-
|
73
|
+
|
51
74
|
# insert css
|
52
75
|
opts[:css].each do |css_path|
|
53
76
|
style = head.add_element("style", {"type" => "text/css"})
|
54
77
|
cdata(File.read(css_path), style)
|
55
78
|
end
|
56
|
-
|
79
|
+
|
57
80
|
# insert css for printing
|
58
81
|
opts[:css_print].each do |css_path|
|
59
82
|
style = head.add_element("style", {"type" => "text/css", "media"=>"print"})
|
60
83
|
cdata(File.read(css_path), style)
|
61
84
|
end
|
62
|
-
|
85
|
+
|
63
86
|
# inster js files
|
64
87
|
opts[:js].each do |js_path|
|
65
88
|
script = head.add_element("script", {"type" => "text/javascript"})
|
66
89
|
cdata(File.read(js_path), script)
|
67
90
|
end
|
68
|
-
|
91
|
+
document_changed()
|
69
92
|
end
|
70
|
-
|
71
|
-
# Surrounds CData tag with c-style comments to remain compatible with normal html.
|
93
|
+
|
94
|
+
# Surrounds CData tag with c-style comments to remain compatible with normal html.
|
72
95
|
# For plain xhtml documents this is not needed.
|
73
96
|
# Example /*<![CDATA[*/\n ...content ... \n/*]]>*/
|
74
97
|
# @param str [String] the string to be enclosed in cdata
|
75
98
|
# @param parent_element [REXML::Element] the element to which cdata should be added
|
76
99
|
# @return [String] CDATA enclosed in c-style comments /**/
|
77
100
|
def cdata(str, parent_element)
|
78
|
-
f = REXML::Formatters::Transitive.new(0) # use Transitive to preserve source formatting
|
79
101
|
# somehow there is a problem with CDATA, any text added after will automatically go into the CDATA
|
80
102
|
# so we have do add a dummy node after the CDATA and then add the text.
|
81
103
|
parent_element.add_text("/*")
|
@@ -83,14 +105,15 @@ module XhtmlReportGenerator
|
|
83
105
|
parent_element.add(REXML::Comment.new("dummy comment to make c-style comments for cdata work"))
|
84
106
|
parent_element.add_text("*/")
|
85
107
|
end
|
86
|
-
|
87
|
-
# Check if the give string is a valid UTF-8 byte sequence. If it is not valid UTF-8, then
|
108
|
+
|
109
|
+
# Check if the give string is a valid UTF-8 byte sequence. If it is not valid UTF-8, then
|
88
110
|
# all invalid bytes are replaced by "\u2e2e" (\xe2\xb8\xae) ('REVERSED QUESTION MARK') because the default
|
89
|
-
# replacement character "\uFFFD" ('QUESTION MARK IN DIAMOND BOX') is two slots wide and might
|
111
|
+
# replacement character "\uFFFD" ('QUESTION MARK IN DIAMOND BOX') is two slots wide and might
|
90
112
|
# destroy mono spaced formatting
|
91
113
|
# @param str [String] of any encoding
|
92
114
|
# @return [String] UTF-8 encoded valid string
|
93
115
|
def encoding_fixer(str)
|
116
|
+
str = str.to_s # catch str = nil
|
94
117
|
#if !str.force_encoding('UTF-8').valid_encoding?
|
95
118
|
# str.encode!('UTF-8', 'ISO-8859-1', {:invalid => :replace, :undef => :replace, :xml => :text})
|
96
119
|
#end
|
@@ -120,7 +143,7 @@ module XhtmlReportGenerator
|
|
120
143
|
end
|
121
144
|
|
122
145
|
# returns the string representation of the xml document
|
123
|
-
# @param indent [Number] indent for child elements. defaults to 0.
|
146
|
+
# @param indent [Number] indent for child elements. defaults to 0.
|
124
147
|
# Note: if you change the indet this might destroy formatting of <pre> sections
|
125
148
|
# @return [String] formatted xml document
|
126
149
|
def to_s(indent = 0)
|
@@ -130,26 +153,652 @@ module XhtmlReportGenerator
|
|
130
153
|
# for compatibility with 1.9.3
|
131
154
|
# @document.write({:output=>output, :indent=>indent, :transitive=>true})
|
132
155
|
# change to Formatters since document.write is deprecated
|
133
|
-
f = REXML::Formatters::Transitive.new(indent)
|
156
|
+
f = REXML::Formatters::Transitive.new(indent)
|
134
157
|
f.write(@document, output)
|
135
158
|
return output
|
136
159
|
end
|
137
|
-
|
160
|
+
|
138
161
|
# Saves the xml document to a file. If no file is given, the file which was used most recently for this Generator
|
139
162
|
# object will be overwritten.
|
140
163
|
# @param file [String] absolute or relative path to the file to which will be written. Default: last file used.
|
141
164
|
# @param mode [String] defaults to 'w', one of the file open modes that allows writing ['r+','w','w+','a','a+']
|
142
165
|
def write(file=@file, mode='w')
|
143
166
|
# instance variables are nil if they were never initialized
|
144
|
-
if file
|
145
|
-
raise "no valid file given"
|
167
|
+
if file.nil?
|
168
|
+
raise "no valid file given: '#{file}'"
|
146
169
|
end
|
147
170
|
@file = file
|
148
171
|
File.open(file, "#{mode}:UTF-8") {|f| f.write(self.to_s.force_encoding(Encoding::UTF_8))}
|
149
172
|
end
|
150
|
-
|
151
|
-
end
|
152
|
-
end
|
153
173
|
|
174
|
+
# This method should be called after every change to the document.
|
175
|
+
# Here we ensure the report is written to disk after each change
|
176
|
+
# if #sync is true. If #sync is false this method does nothing
|
177
|
+
def document_changed()
|
178
|
+
if @sync
|
179
|
+
if @file.nil?
|
180
|
+
raise "You must call #write at least once before you can enable synced mode"
|
181
|
+
end
|
182
|
+
write()
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# creates the basic page layout and sets the current Element to the main content area (middle div)
|
187
|
+
# @example The middle div is matched by the following xPath
|
188
|
+
# //body/div[@id='middle']
|
189
|
+
# @param title [String] the title of the document
|
190
|
+
# @param layout [Fixnum] one of 0,1,2,3 where 0 means minimal layout without left and right table of contents,
|
191
|
+
# 1 means only left toc, 2 means only right toc, and 3 means full layout with left and right toc.
|
192
|
+
def create_layout(title, layout=3)
|
193
|
+
raise "invalid layout selector, choose from 0..3" if (layout < 0) || (layout > 3)
|
194
|
+
|
195
|
+
@body = @document.elements["//body"]
|
196
|
+
# only add the layout if it is not already there
|
197
|
+
if !@layout
|
198
|
+
head = @body.add_element("div", {"class" => "head", "id" => "head"})
|
199
|
+
head.add_element("button", {"id" => "pre_toggle_linewrap"}).add_text("Toggle Linewrap")
|
200
|
+
|
201
|
+
if (layout & 0x1) != 0
|
202
|
+
div = @body.add_element("div", {"class" => "lefttoc split split-horizontal", "id" => "ltoc"})
|
203
|
+
div.add_text("Table of Contents")
|
204
|
+
div.add_element("br")
|
205
|
+
end
|
206
|
+
|
207
|
+
@div_middle = @body.add_element("div", {"class" => "middle split split-horizontal", "id" => "middle"})
|
208
|
+
|
209
|
+
if (layout & 0x2) != 0
|
210
|
+
div = @body.add_element("div", {"class" => "righttoc split split-horizontal", "id" => "rtoc"})
|
211
|
+
div.add_text("Quick Links")
|
212
|
+
div.add_element("br");div.add_element("br")
|
213
|
+
end
|
214
|
+
|
215
|
+
@body.add_element("p", {"class" => "#{layout}", "id" => "layout"}).add_text("this text should be hidden")
|
216
|
+
|
217
|
+
@layout = true
|
218
|
+
end
|
219
|
+
@current = @document.elements["//body/div[@id='middle']"]
|
220
|
+
set_title(title)
|
221
|
+
document_changed()
|
222
|
+
end
|
223
|
+
|
224
|
+
# sets the title of the document in the <head> section as well as in the layout header div
|
225
|
+
# create_layout must be called before!
|
226
|
+
# @param title [String] the text which will be insertead
|
227
|
+
def set_title(title)
|
228
|
+
if !@layout
|
229
|
+
raise "call create_layout first"
|
230
|
+
end
|
231
|
+
pagetitle = @document.elements["//head/title"]
|
232
|
+
pagetitle.text = title
|
233
|
+
div = @document.elements["//body/div[@id='head']"]
|
234
|
+
div.text = title
|
235
|
+
document_changed()
|
236
|
+
end
|
237
|
+
|
238
|
+
# returns the title text of the report
|
239
|
+
# @return [String] The title of the report
|
240
|
+
def get_title()
|
241
|
+
pagetitle = @document.elements["//head/title"]
|
242
|
+
return pagetitle.text
|
243
|
+
end
|
244
|
+
|
245
|
+
# set the current element to the element or first element matched by the xpath expression.
|
246
|
+
# The current element is the one which can be modified through highlighting.
|
247
|
+
# @param xpath [REXML::Element|String] the element or an xpath string
|
248
|
+
def set_current!(xpath)
|
249
|
+
if xpath.is_a?(REXML::Element)
|
250
|
+
@current = xpath
|
251
|
+
elsif xpath.is_a?(String)
|
252
|
+
@current = @document.elements[xpath]
|
253
|
+
else
|
254
|
+
raise "xpath is neither a String nor a REXML::Element"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# returns the current xml element
|
259
|
+
# @return [REXML::Element] the xml element after which the following elements will be added
|
260
|
+
def get_current()
|
261
|
+
return @current
|
262
|
+
end
|
263
|
+
|
264
|
+
# returns the plain text without any xml tags of the specified element and all its children
|
265
|
+
# @param el [REXML::Element] The element from which to fetch the text children. Defaults to @current
|
266
|
+
# @param recursive [Boolean] whether or not to recurse into the children of the given "el"
|
267
|
+
# @return [String] text contents of xml node
|
268
|
+
def get_element_text(el = @current, recursive = true)
|
269
|
+
out = ""
|
270
|
+
el.to_a.each { |child|
|
271
|
+
if child.is_a?(REXML::Text)
|
272
|
+
out << child.value()
|
273
|
+
else
|
274
|
+
if recursive
|
275
|
+
out << get_element_text(child, true)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
}
|
279
|
+
return out
|
280
|
+
end
|
281
|
+
|
282
|
+
# @param elem [REXML::Element]
|
283
|
+
# @return [String]
|
284
|
+
def element_to_string(elem)
|
285
|
+
f = REXML::Formatters::Transitive.new(0) # use Transitive to preserve source formatting (e.g. <pre> tags)
|
286
|
+
out = ""
|
287
|
+
f.write(elem, out)
|
288
|
+
return out
|
289
|
+
end
|
290
|
+
|
291
|
+
# @see #code
|
292
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
293
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
294
|
+
# @return [String] the code including <pre> tags as a string
|
295
|
+
def get_code_html(attrs={}, &block)
|
296
|
+
temp = REXML::Element.new("pre")
|
297
|
+
temp.add_attributes(attrs)
|
298
|
+
raise "Block argument is mandatory" unless block_given?
|
299
|
+
text = encoding_fixer(block.call())
|
300
|
+
temp.add_text(text)
|
301
|
+
element_to_string(temp)
|
302
|
+
end
|
303
|
+
|
304
|
+
# Appends a <pre> node after the @current node
|
305
|
+
# @param attrs [Hash] attributes for the <pre> element. The following classes can be passed as attributes and are predefined with a different
|
306
|
+
# background for your convenience !{"class" => "code0"} (light-blue), !{"class" => "code1"} (red-brown),
|
307
|
+
# !{"class" => "code2"} (light-green), !{"class" => "code3"} (light-yellow). You may also specify your own background
|
308
|
+
# as follows: !{"style" => "background: #FF00FF;"}.
|
309
|
+
# @yieldreturn [String] the text to be added to the <pre> element
|
310
|
+
# @return [REXML::Element] the Element which was just added
|
311
|
+
def code(attrs={}, &block)
|
312
|
+
temp = REXML::Element.new("pre")
|
313
|
+
temp.add_attributes(attrs)
|
314
|
+
@div_middle.insert_after(@current, temp)
|
315
|
+
@current = temp
|
316
|
+
raise "Block argument is mandatory" unless block_given?
|
317
|
+
text = encoding_fixer(block.call())
|
318
|
+
@current.add_text(text)
|
319
|
+
document_changed()
|
320
|
+
return @current
|
321
|
+
end
|
322
|
+
|
323
|
+
# Appends a <script> node after the @current node
|
324
|
+
# @param attrs [Hash] attributes for the <script> element. The following attribute is added by default:
|
325
|
+
# type="text/javascript"
|
326
|
+
# @yieldreturn [String] the actual javascript code to be added to the <script> element
|
327
|
+
# @return [REXML::Element] the Element which was just added
|
328
|
+
def javascript(attrs={}, &block)
|
329
|
+
default_attrs = {"type" => "text/javascript"}
|
330
|
+
attrs = default_attrs.merge(attrs)
|
331
|
+
temp = REXML::Element.new("script")
|
332
|
+
temp.add_attributes(attrs)
|
333
|
+
@div_middle.insert_after(@current, temp)
|
334
|
+
@current = temp
|
335
|
+
raise "Block argument is mandatory" unless block_given?
|
336
|
+
script_content = encoding_fixer(block.call())
|
337
|
+
cdata(script_content, @current)
|
338
|
+
document_changed()
|
339
|
+
return @current
|
340
|
+
end
|
341
|
+
|
342
|
+
# @see #content
|
343
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
344
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
345
|
+
# @return [String] the code including <pre> tags as a string
|
346
|
+
def get_content_html(attrs={}, &block)
|
347
|
+
temp = REXML::Element.new("p")
|
348
|
+
temp.add_attributes(attrs)
|
349
|
+
raise "Block argument is mandatory" unless block_given?
|
350
|
+
text = encoding_fixer(block.call())
|
351
|
+
temp.add_text(text)
|
352
|
+
element_to_string(temp)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Appends a <p> node after the @current node
|
356
|
+
# @param attrs [Hash] attributes for the <p> element
|
357
|
+
# @yieldreturn [String] the text to be added to the <p> element
|
358
|
+
# @return [REXML::Element] the Element which was just added
|
359
|
+
def content(attrs={}, &block)
|
360
|
+
temp = REXML::Element.new("p")
|
361
|
+
temp.add_attributes(attrs)
|
362
|
+
@div_middle.insert_after(@current, temp)
|
363
|
+
@current = temp
|
364
|
+
raise "Block argument is mandatory" unless block_given?
|
365
|
+
text = encoding_fixer(block.call())
|
366
|
+
@current.add_text(text)
|
367
|
+
document_changed()
|
368
|
+
return @current
|
369
|
+
end
|
370
|
+
|
371
|
+
# insert arbitrary xml code after the @current element in the content pane (div middle)
|
372
|
+
# @param text [String] valid xhtml code which is included into the document
|
373
|
+
# @return [REXML::Element] the Element which was just added
|
374
|
+
def html(text)
|
375
|
+
# we need to create a new document with a pseudo root becaus having multiple nodes at top
|
376
|
+
# level is not valid xml
|
377
|
+
doc = REXML::Document.new("<root>"+text.to_s+"</root>")
|
378
|
+
# then we move all children of root to the actual div middle element and insert after current
|
379
|
+
for i in doc.root.to_a do
|
380
|
+
@div_middle.insert_after(@current, i)
|
381
|
+
@current = i
|
382
|
+
end
|
383
|
+
document_changed()
|
384
|
+
return @current
|
385
|
+
end
|
386
|
+
|
387
|
+
# @see #link
|
388
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
389
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
390
|
+
# @return [String] the code including <a> tags as a string
|
391
|
+
def get_link_html(href, attrs={}, &block)
|
392
|
+
temp = REXML::Element.new("a")
|
393
|
+
attrs.merge!({"href" => href})
|
394
|
+
temp.add_attributes(attrs)
|
395
|
+
raise "Block argument is mandatory" unless block_given?
|
396
|
+
text = encoding_fixer(block.call())
|
397
|
+
temp.add_text(text)
|
398
|
+
element_to_string(temp)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Appends a <a href = > node after the @current nodes
|
402
|
+
# @param href [String] this is the
|
403
|
+
# @param attrs [Hash] attributes for the <a> element
|
404
|
+
# @yieldreturn [String] the text to be added to the <a> element
|
405
|
+
# @return [REXML::Element] the Element which was just added
|
406
|
+
def link(href, attrs={}, &block)
|
407
|
+
temp = REXML::Element.new("a")
|
408
|
+
attrs.merge!({"href" => href})
|
409
|
+
temp.add_attributes(attrs)
|
410
|
+
@div_middle.insert_after(@current, temp)
|
411
|
+
@current = temp
|
412
|
+
raise "Block argument is mandatory" unless block_given?
|
413
|
+
text = encoding_fixer(block.call())
|
414
|
+
@current.add_text(text)
|
415
|
+
document_changed()
|
416
|
+
return @current
|
417
|
+
end
|
418
|
+
|
419
|
+
# @see #image
|
420
|
+
# Instead of adding content to the report, this method returns the produced html code as a string.
|
421
|
+
# This can be used to insert code into #custom_table (with the option data_is_xhtml: true)
|
422
|
+
# @return [String] the code including <pre> tags as a string
|
423
|
+
def get_image_html(path, attributes = {})
|
424
|
+
# read image as binary and do a base64 encoding
|
425
|
+
binary_data = Base64.strict_encode64(IO.binread(path))
|
426
|
+
type = File.extname(path).gsub('.', '')
|
427
|
+
# create the element
|
428
|
+
temp = REXML::Element.new("img")
|
429
|
+
# add the picture
|
430
|
+
temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
|
431
|
+
temp.add_attributes(attributes)
|
432
|
+
element_to_string(temp)
|
433
|
+
end
|
434
|
+
|
435
|
+
# @param path [String] absolute or relative path to the image that should be inserted into the report
|
436
|
+
# @param attributes [Hash] attributes for the <img> element, any valid html attributes can be specified
|
437
|
+
# you may specify attributes such "alt", "height", "width"
|
438
|
+
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
439
|
+
def image(path, attributes = {})
|
440
|
+
# read image as binary and do a base64 encoding
|
441
|
+
binary_data = Base64.strict_encode64(IO.binread(path))
|
442
|
+
type = File.extname(path).gsub('.', '')
|
443
|
+
# create the element
|
444
|
+
temp = REXML::Element.new("img")
|
445
|
+
# add the picture
|
446
|
+
temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
|
447
|
+
temp.add_attributes(attributes)
|
448
|
+
|
449
|
+
@div_middle.insert_after(@current, temp)
|
450
|
+
@current = temp
|
451
|
+
document_changed()
|
452
|
+
return @current
|
453
|
+
end
|
454
|
+
|
455
|
+
# Scans all REXML::Text children of an REXML::Element for any occurrences of regex.
|
456
|
+
# The text will be matched as one, not line by line as you might think.
|
457
|
+
# If you want to write a regexp matching multiple lines keep in mind that the dot "." by default doesn't
|
458
|
+
# match newline characters. Consider using the "m" option (e.g. /regex/m ) which makes dot match newlines
|
459
|
+
# or match newlines explicitly.
|
460
|
+
# highlight_captures then puts a <span> </span> tag around all captures of the regex
|
461
|
+
# NOTE: nested captures are not supported and don't make sense in this context!!
|
462
|
+
# @param regex [Regexp] a regular expression that will be matched
|
463
|
+
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
464
|
+
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches, by default the last inserted element will be scanned
|
465
|
+
# @return [Fixnum] the number of highlighted captures
|
466
|
+
def highlight_captures(regex, color="y", el = @current)
|
467
|
+
# get all children of the current node
|
468
|
+
arr = el.to_a()
|
469
|
+
num_matches = 0
|
470
|
+
# first we have to detach all children from parent, otherwise we can cause ordering issues
|
471
|
+
arr.each {|i| i.remove() }
|
472
|
+
# depth first recursion into grand-children
|
473
|
+
for i in arr do
|
474
|
+
if i.is_a?(REXML::Text)
|
475
|
+
# in general a text looks as follows:
|
476
|
+
# .*(matchstring|.*)*
|
477
|
+
|
478
|
+
# We get an array of [[start,length], [start,length], ...] for all our regex SUB-matches
|
479
|
+
positions = i.value().enum_for(:scan, regex).flat_map {
|
480
|
+
# Regexp.last_match is a MatchData object, the index 0 is the entire match, 1 to n are captures
|
481
|
+
array = Array.new
|
482
|
+
for k in 1..Regexp.last_match.length - 1 do
|
483
|
+
array.push([Regexp.last_match.begin(k),
|
484
|
+
Regexp.last_match.end(k)-Regexp.last_match.begin(k)])
|
485
|
+
end
|
486
|
+
# return the array for the flat_map
|
487
|
+
array
|
488
|
+
}
|
489
|
+
num_matches += positions.length
|
490
|
+
if ["y","r","g","b"].include?(color)
|
491
|
+
attr = {"class" => color}
|
492
|
+
elsif color.match(/^#[A-Fa-f0-9]{6}$/)
|
493
|
+
attr = {"style" => "background: #{color};"}
|
494
|
+
else
|
495
|
+
raise "invalid color: #{color}"
|
496
|
+
end
|
497
|
+
replace_text_with_elements(el, i, "span", attr, positions)
|
498
|
+
else
|
499
|
+
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
500
|
+
num_matches += highlight_captures(regex, color, i)
|
501
|
+
el.add(i)
|
502
|
+
end # if i.is_a?(REXML::Text)
|
503
|
+
end # for i in arr do
|
504
|
+
document_changed()
|
505
|
+
return num_matches
|
506
|
+
end
|
507
|
+
|
508
|
+
# Scans all REXML::Text children of an REXML::Element for any occurrences of regex.
|
509
|
+
# The text will be matched as one, not line by line as you might think.
|
510
|
+
# If you want to write a regexp matching multiple lines keep in mind that the dot "." by default doesn't
|
511
|
+
# match newline characters. Consider using the "m" option (e.g. /regex/m ) which makes dot match newlines
|
512
|
+
# or match newlines explicitly.
|
513
|
+
# highlight then puts a <span> </span> tag around all matches of regex
|
514
|
+
# @param regex [Regexp] a regular expression that will be matched
|
515
|
+
# @param color [String] either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")
|
516
|
+
# @param el [REXML::Element] the Element (scope) which will be searched for pattern matches
|
517
|
+
# @return [Fixnum] the number of highlighted captures
|
518
|
+
def highlight(regex, color="y", el = @current)
|
519
|
+
# get all children of the current node
|
520
|
+
arr = el.to_a()
|
521
|
+
num_matches = 0
|
522
|
+
#puts arr.inspect
|
523
|
+
# first we have to detach all children from parent, otherwise we can cause ordering issues
|
524
|
+
arr.each {|i| i.remove() }
|
525
|
+
# depth first recursion into grand-children
|
526
|
+
for i in arr do
|
527
|
+
#puts i.class.to_s()
|
528
|
+
if i.is_a?(REXML::Text)
|
529
|
+
# in general a text looks as follows:
|
530
|
+
# .*(matchstring|.*)*
|
531
|
+
|
532
|
+
# We get an array of [[start,length], [start,length], ...] for all our regex matches
|
533
|
+
positions = i.value().enum_for(:scan, regex).map {
|
534
|
+
[Regexp.last_match.begin(0),
|
535
|
+
Regexp.last_match.end(0)-Regexp.last_match.begin(0)]
|
536
|
+
}
|
537
|
+
num_matches += positions.length
|
538
|
+
if ["y","r","g","b"].include?(color)
|
539
|
+
attr = {"class" => color}
|
540
|
+
elsif color.match(/^#[A-Fa-f0-9]{6}$/)
|
541
|
+
attr = {"style" => "background: #{color};"}
|
542
|
+
else
|
543
|
+
raise "invalid color: #{color}"
|
544
|
+
end
|
545
|
+
replace_text_with_elements(el, i, "span", attr, positions)
|
546
|
+
else
|
547
|
+
# for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
|
548
|
+
# puts "recurse"
|
549
|
+
num_matches += highlight(regex, color, i)
|
550
|
+
el.add(i)
|
551
|
+
end # if i.is_a?(REXML::Text)
|
552
|
+
end # for i in arr do
|
553
|
+
document_changed()
|
554
|
+
return num_matches
|
555
|
+
end
|
556
|
+
|
557
|
+
# creates a html table from two dimensional array of the form Array [row] [col]
|
558
|
+
# @param table_data [Array<Array>] of the form Array [row] [col] containing all data, the '.to_s' method will be called on each element,
|
559
|
+
# @param headers [Number] either of 0, 1, 2, 3. Where 0 is no headers (<th>) at all, 1 is only the first row,
|
560
|
+
# 2 is only the first column and 3 is both, first row and first column as <th> elements. Every other number
|
561
|
+
# is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3
|
562
|
+
# @return [REXML::Element] the Element which was just added
|
563
|
+
def table(table_data, headers=0, table_attrs={}, tr_attrs={}, th_attrs={}, td_attrs={})
|
564
|
+
opts = {
|
565
|
+
headers: headers,
|
566
|
+
data_is_xhtml: false,
|
567
|
+
table_attrs: table_attrs,
|
568
|
+
th_attrs: th_attrs,
|
569
|
+
tr_attrs: tr_attrs,
|
570
|
+
td_attrs: td_attrs,
|
571
|
+
}
|
572
|
+
custom_table(table_data, opts)
|
573
|
+
end
|
574
|
+
|
575
|
+
# creates a html table from two dimensional array of the form Array [row] [col]
|
576
|
+
# @param table_data [Array<Array>] of the form Array [row] [col] containing all data, the '.to_s' method will be called on each element,
|
577
|
+
# @option opts [Number] :headers either of 0, 1, 2, 3. Where 0 is no headers (<th>) at all, 1 is only the first row,
|
578
|
+
# 2 is only the first column and 3 is both, first row and first column as <th> elements. Every other number
|
579
|
+
# is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3
|
580
|
+
# @option opts [Boolean] :data_is_xhtml defaults to false, if true table_data is inserted as xhtml without any sanitation or escaping.
|
581
|
+
# This way a table can be used for custom layouts.
|
582
|
+
# @option opts [Hash] :table_attrs html attributes for the <table> tag
|
583
|
+
# @option opts [Hash] :th_attrs html attributes for the <th> tag
|
584
|
+
# @option opts [Hash] :tr_attrs html attributes for the <tr> tag
|
585
|
+
# @option opts [Hash] :td_attrs html attributes for the <td> tag
|
586
|
+
# @option opts [Array<Hash>] :special Array of hashes for custom attributes on specific cells (<td> only) of the table
|
587
|
+
# @example Example of the :special attributes
|
588
|
+
# opts[:special] = [
|
589
|
+
# {
|
590
|
+
# col_title: 'rx_DroppedFrameCount', # string or regexp or nil # if neither title nor index are present, the condition is evaluated for all <td> cells
|
591
|
+
# col_index: 5..7, # Fixnum, Range or nil # index has precedence over title
|
592
|
+
# row_title: 'D_0_BE_iMix', # string or regexp or nil
|
593
|
+
# row_index: 6, # Fixnum, Range or nil
|
594
|
+
# condition: Proc.new { |e| Integer(e) != 0 }, # a proc
|
595
|
+
# attributes: {"style" => "background-color: #DB7093;"},
|
596
|
+
# },
|
597
|
+
# ]
|
598
|
+
# @return [REXML::Element] the Element which was just added
|
599
|
+
def custom_table(table_data, opts = {})
|
600
|
+
defaults = {
|
601
|
+
headers: 0,
|
602
|
+
data_is_xhtml: false,
|
603
|
+
table_attrs: {},
|
604
|
+
th_attrs: {},
|
605
|
+
tr_attrs: {},
|
606
|
+
td_attrs: {},
|
607
|
+
special: [],
|
608
|
+
}
|
609
|
+
o = defaults.merge(opts)
|
610
|
+
|
611
|
+
temp = REXML::Element.new("table")
|
612
|
+
temp.add_attributes(o[:table_attrs])
|
613
|
+
row_titles = table_data.collect{|row| row[0].to_s}
|
614
|
+
col_titles = table_data[0].collect{|title| title.to_s}
|
615
|
+
|
616
|
+
for i in 0..table_data.length-1 do # row
|
617
|
+
row = temp.add_element("tr", o[:tr_attrs])
|
618
|
+
for j in 0..table_data[i].length-1 do # column
|
619
|
+
if (i == 0 && (0x1 & o[:headers])==0x1)
|
620
|
+
col = row.add_element("th", o[:th_attrs])
|
621
|
+
elsif (j == 0 && (0x2 & o[:headers])==0x2)
|
622
|
+
col = row.add_element("th", o[:th_attrs])
|
623
|
+
elsif ((i == 0 || j ==0) && (0x3 & o[:headers])==0x3)
|
624
|
+
col = row.add_element("th", o[:th_attrs])
|
625
|
+
else
|
626
|
+
# we need to deepcopy the attributes
|
627
|
+
_td_attrs = Marshal.load(Marshal.dump(o[:td_attrs]))
|
628
|
+
|
629
|
+
# check all special criteria
|
630
|
+
o[:special].each do |h|
|
631
|
+
# check if the current cell is a candidate for special
|
632
|
+
if !h[:col_index].nil?
|
633
|
+
if h[:col_index].is_a?(Range)
|
634
|
+
next if (!h[:col_index].include?(j)) # skip if not in range
|
635
|
+
elsif h[:col_index].is_a?(Integer)
|
636
|
+
next if (h[:col_index] != j) # skip if not at index
|
637
|
+
end
|
638
|
+
elsif !h[:col_title].nil?
|
639
|
+
next if !col_titles[j].match(h[:col_title])
|
640
|
+
end
|
641
|
+
# check if the current cell is a candidate for special
|
642
|
+
if !h[:row_index].nil?
|
643
|
+
if h[:row_index].is_a?(Range)
|
644
|
+
next if (!h[:row_index].include?(i)) # skip if not in range
|
645
|
+
elsif h[:row_index].is_a?(Integer)
|
646
|
+
next if (h[:row_index] != i) # skip if not at index
|
647
|
+
end
|
648
|
+
elsif !h[:row_title].nil?
|
649
|
+
next if !row_titles[i].match(h[:row_title])
|
650
|
+
end
|
651
|
+
|
652
|
+
# here we are a candidate for special, so we check if we meet the condition
|
653
|
+
# puts h[:attributes].inspect
|
654
|
+
# puts "cell value row #{i} col #{j}: #{table_data[i][j]}"
|
655
|
+
# puts h[:condition].call(table_data[i][j]).inspect
|
656
|
+
if h[:condition].call(table_data[i][j])
|
657
|
+
h[:attributes].each { |attr, val|
|
658
|
+
# debug, verify deepcopy
|
659
|
+
# puts "objects are equal: #{_td_attrs[attr].equal?(o[:td_attrs][attr])}"
|
660
|
+
if !_td_attrs[attr].nil?
|
661
|
+
# assume the existing attribute is a string (other types don't make much sense for html)
|
662
|
+
_td_attrs[attr] << val
|
663
|
+
else
|
664
|
+
# create the attribute if it is not already there
|
665
|
+
_td_attrs[attr] = val
|
666
|
+
end
|
667
|
+
}
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
col = row.add_element("td", _td_attrs)
|
672
|
+
end
|
673
|
+
if o[:data_is_xhtml]
|
674
|
+
# we need to create a new document with a pseudo root because having multiple nodes at top
|
675
|
+
# level is not valid xml
|
676
|
+
doc = REXML::Document.new("<root>" + table_data[i][j].to_s + "</root>")
|
677
|
+
# then we move all children of root to the actual div middle element and insert after current
|
678
|
+
for elem in doc.root.to_a do
|
679
|
+
if elem.is_a?(REXML::Text)
|
680
|
+
# due to reasons unclear to me, text needs special treatment
|
681
|
+
col.add_text(elem)
|
682
|
+
else
|
683
|
+
col.add_element(elem) # add the td element
|
684
|
+
end
|
685
|
+
end
|
686
|
+
else
|
687
|
+
col.add_text(table_data[i][j].to_s)
|
688
|
+
end
|
689
|
+
end # for j in 0..table_data[i].length-1 do # column
|
690
|
+
end # for i in 0..table_data.length-1 do # row
|
691
|
+
|
692
|
+
@div_middle.insert_after(@current, temp)
|
693
|
+
@current = temp
|
694
|
+
document_changed()
|
695
|
+
return @current
|
696
|
+
end
|
697
|
+
|
698
|
+
|
699
|
+
# Appends a new heading element to body, and sets current to this new heading
|
700
|
+
# @param tag_type [String] specifiy "h1", "h2", "h3" for the heading, defaults to "h1"
|
701
|
+
# @param attrs [Hash] attributes for the <h#> element, any valid html attributes can be specified
|
702
|
+
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
703
|
+
# use the class "onlyrtoc" or "bothtoc" to add a heading only to the right toc or to both tocs respectively
|
704
|
+
# @yieldreturn [String] the text to be added to the <h#> element
|
705
|
+
# @return [REXML::Element] the Element which was just added
|
706
|
+
def heading(tag_type="h1", attrs={}, &block)
|
707
|
+
temp = REXML::Element.new(tag_type)
|
708
|
+
temp.add_attributes(attrs)
|
709
|
+
|
710
|
+
@div_middle.insert_after(@current, temp)
|
711
|
+
@current = temp
|
712
|
+
raise "Block argument is mandatory" unless block_given?
|
713
|
+
text = encoding_fixer(block.call())
|
714
|
+
@current.text = text
|
715
|
+
document_changed()
|
716
|
+
return @current
|
717
|
+
end
|
718
|
+
|
719
|
+
# Inserts a new heading element at the very beginning of the middle div section, and points @current to this heading
|
720
|
+
# @param tag_type [String] specifiy "h1", "h2", "h3" for the heading, defaults to "h1"
|
721
|
+
# @param attrs [Hash] attributes for the <h#> element, any valid html attributes can be specified
|
722
|
+
# @option attrs [String] "class" by default every heading is added to the left table of contents (toc)
|
723
|
+
# use the class "onlyrtoc" or "bothtoc" to add a heading only to the right toc or to both tocs respectively
|
724
|
+
# @yieldreturn [String] the text to be added to the <h#> element
|
725
|
+
# @return [REXML::Element] the Element which was just added
|
726
|
+
def heading_top(tag_type="h1", attrs={}, &block)
|
727
|
+
temp = REXML::Element.new(tag_type)
|
728
|
+
temp.add_attributes(attrs)
|
729
|
+
|
730
|
+
# check if there are any child elements
|
731
|
+
if @div_middle.has_elements?()
|
732
|
+
# insert before the first child of div middle
|
733
|
+
@div_middle.insert_before("//div[@id='middle']/*[1]", temp)
|
734
|
+
else
|
735
|
+
# middle is empty, just insert the heading
|
736
|
+
@div_middle.insert_after(@current, temp)
|
737
|
+
end
|
738
|
+
|
739
|
+
@current = temp
|
740
|
+
raise "Block argument is mandatory" unless block_given?
|
741
|
+
text = encoding_fixer(block.call())
|
742
|
+
@current.text = text
|
743
|
+
document_changed()
|
744
|
+
return @current
|
745
|
+
end
|
746
|
+
|
747
|
+
# Helper Method for the highlight methods. it will introduce specific xhtml tags around parts of a text child of an xml element.
|
748
|
+
# @example
|
749
|
+
# we have the following xml part
|
750
|
+
# <test>
|
751
|
+
# some arbitrary
|
752
|
+
# text child content
|
753
|
+
# </test>
|
754
|
+
# now we call replace_text_with_elements to place <span> around the word "arbitrary"
|
755
|
+
# =>
|
756
|
+
# <test>
|
757
|
+
# some <span>arbitrary</span>
|
758
|
+
# text child content
|
759
|
+
# </test>
|
760
|
+
# @param parent [REXML::Element] the parent to which "element" should be attached after parsing, e.g. <test>
|
761
|
+
# @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
|
762
|
+
# @param tagname [String] the tag that will be introduced as <tagname> at the indices specified
|
763
|
+
# @param attribs [Hash] Attributes that will be added to the inserted tag e.g. <tagname attrib="test">
|
764
|
+
# @param index_length_array [Array] Array of the form [[index, lenght], [index, lenght], ...] that specifies
|
765
|
+
# the start position and length of the substring around which the tags will be introduced
|
766
|
+
def replace_text_with_elements(parent, element, tagname, attribs, index_length_array)
|
767
|
+
last_end = 0
|
768
|
+
index = 0
|
769
|
+
#puts index_length_array.inspect
|
770
|
+
#puts element.inspect
|
771
|
+
for j in index_length_array do
|
772
|
+
# reattach normal (unmatched) text
|
773
|
+
if j[0] > last_end
|
774
|
+
# text = REXML::Text.new(element.value()[ last_end, j[0] - last_end ])
|
775
|
+
# parent.add_text(text)
|
776
|
+
# add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
|
777
|
+
parent.add_text( element.value()[ last_end, j[0] - last_end ] )
|
778
|
+
end
|
779
|
+
#create the tag node with attributes and add the text to it
|
780
|
+
tag = parent.add_element(REXML::Element.new(tagname), attribs)
|
781
|
+
tag.add_text(element.value()[ j[0], j[1] ])
|
782
|
+
last_end = j[0]+j[1]
|
783
|
+
|
784
|
+
# in the last round check for any remaining text
|
785
|
+
if index == index_length_array.length - 1
|
786
|
+
if last_end < element.value().length
|
787
|
+
# text = REXML::Text.new(element.value()[ last_end, element.value().length - last_end ])
|
788
|
+
# parent.add(text)
|
789
|
+
# add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
|
790
|
+
parent.add_text( element.value()[ last_end, element.value().length - last_end ] )
|
791
|
+
end
|
792
|
+
end
|
793
|
+
index += 1
|
794
|
+
end # for j in positions do
|
795
|
+
|
796
|
+
# don't forget to reattach the textnode if there are no regex matches at all
|
797
|
+
if index == 0
|
798
|
+
parent.add(element)
|
799
|
+
end
|
154
800
|
|
801
|
+
end # replace_text_with_elements
|
155
802
|
|
803
|
+
end # Generator
|
804
|
+
end # XhtmlReportGenerator
|