xhtml_report_generator 3.1.2 → 4.0.0
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 +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
|