Module: Custom

Defined in:
lib/xhtml_report_generator/custom.rb

Overview

The module name doesn’t matter, just make sure at the end to ‘extend’ it because it will be ‘eval’ed by the initialize method of the XhtmlReportGenerator::Generator class.

Instance Method Summary collapse

Instance Method Details

#code(attrs = {}, &block) ⇒ REXML::Element

Appends a <pre> node after the @current node

Parameters:

  • attrs (Hash) (defaults to: {})

    attributes for the <pre> element

Yield Returns:

  • (String)

    the text to be added to the <pre> element

Returns:

  • (REXML::Element)

    the Element which was just added



81
82
83
84
85
86
87
88
89
90
# File 'lib/xhtml_report_generator/custom.rb', line 81

def code(attrs={}, &block)
  temp = REXML::Element.new("pre")
  temp.add_attributes(attrs)
  @div_middle.insert_after(@current, temp)
  @current = temp
  raise "Block argument is mandatory" unless block_given?
  text = block.call()
  @current.add_text(text)
  return @current
end

#content(attrs = {}, &block) ⇒ REXML::Element

Appends a <p> node after the @current node

Parameters:

  • attrs (Hash) (defaults to: {})

    attributes for the <p> element

Yield Returns:

  • (String)

    the text to be added to the <p> element

Returns:

  • (REXML::Element)

    the Element which was just added



96
97
98
99
100
101
102
103
104
105
# File 'lib/xhtml_report_generator/custom.rb', line 96

def content(attrs={}, &block)
  temp = REXML::Element.new("p")
  temp.add_attributes(attrs)
  @div_middle.insert_after(@current, temp)
  @current = temp
  raise "Block argument is mandatory" unless block_given?
  text = block.call()
  @current.add_text(text)
  return @current
end

#create_layout(title, layout = 3) ⇒ Object

creates the basic page layout and sets the current Element to the main content area (middle div)

Examples:

The middle div is matched by the following xPath

//body/div[@class='middle']

Parameters:

  • title (String)

    the title of the document

  • layout (Fixnum) (defaults to: 3)

    one of 0,1,2,3 where 0 means minimal layout without left and right table of contents, 1 means only left toc, 2 means only right toc, and 3 means full layout with left and right toc.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/xhtml_report_generator/custom.rb', line 11

def create_layout(title, layout=3)
  raise "invalid layout selector, choose from 0..3" if (layout < 0) || (layout > 3)
  
  @body = @document.elements["//body"]
  # only add the layout if it is not already there
  if !@layout
    @body.add_element("div", {"class" => "head"})
    
    if (layout & 0x1) != 0
    div = @body.add_element("div", {"class" => "lefttoc", "id" => "ltoc"})
    div.add_text("Table of Contents")
    div.add_element("br")
    end
    
    if (layout & 0x2) != 0
    div = @body.add_element("div", {"class" => "righttoc", "id" => "rtoc"})
    div.add_text("Quick Links")
    div.add_element("br");div.add_element("br")
    end
    
    @div_middle = @body.add_element("div", {"class" => "middle"})
    @layout = true
  end
  @current = @document.elements["//body/div[@class='middle']"]
  set_title(title)
end

#get_currentREXML::Element

returns the current xml element

Returns:

  • (REXML::Element)

    the xml element after which the following elements will be added



73
74
75
# File 'lib/xhtml_report_generator/custom.rb', line 73

def get_current()
  return @current
end

#get_titleString

returns the title text of the report

Returns:

  • (String)

    The title of the report



53
54
55
56
# File 'lib/xhtml_report_generator/custom.rb', line 53

def get_title()
  pagetitle = @document.elements["//head/title"]
  return pagetitle.text
end

#heading(tag_type = "h1", attrs = {}, &block) ⇒ REXML::Element

Appends a new heading element to body, and sets current to this new heading

Parameters:

  • tag_type (String) (defaults to: "h1")

    specifiy “h1”, “h2”, “h3” for the heading, defaults to “h1”

  • attrs (Hash) (defaults to: {})

    attributes for the <h#> element, any valid html attributes can be specified

Options Hash (attrs):

  • "class" (String)

    by default every heading is added to the left table of contents (toc) use the class “onlyrtoc” or “bothtoc” to add a heading only to the right toc or to both tocs respectively

Yield Returns:

  • (String)

    the text to be added to the <h#> element

Returns:

  • (REXML::Element)

    the Element which was just added



246
247
248
249
250
251
252
253
254
255
256
# File 'lib/xhtml_report_generator/custom.rb', line 246

def heading(tag_type="h1", attrs={}, &block)
  temp = REXML::Element.new(tag_type)
  temp.add_attributes(attrs)

  @div_middle.insert_after(@current, temp)
  @current = temp
  raise "Block argument is mandatory" unless block_given?
  text = block.call()
  @current.text = text
  return @current
end

#heading_top(tag_type = "h1", attrs = {}, &block) ⇒ REXML::Element

Inserts a new heading element at the very beginning of the middle div section, and points @current to this heading

Parameters:

  • tag_type (String) (defaults to: "h1")

    specifiy “h1”, “h2”, “h3” for the heading, defaults to “h1”

  • attrs (Hash) (defaults to: {})

    attributes for the <h#> element, any valid html attributes can be specified

Options Hash (attrs):

  • "class" (String)

    by default every heading is added to the left table of contents (toc) use the class “onlyrtoc” or “bothtoc” to add a heading only to the right toc or to both tocs respectively

Yield Returns:

  • (String)

    the text to be added to the <h#> element

Returns:

  • (REXML::Element)

    the Element which was just added



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/xhtml_report_generator/custom.rb', line 265

def heading_top(tag_type="h1", attrs={}, &block)
  temp = REXML::Element.new(tag_type)
  temp.add_attributes(attrs)
  
  # check if there are any child elements 
  if @div_middle.has_elements?()
    # insert before the first child of div middle
    @div_middle.insert_before("//div[@class='middle']/*[1]", temp)
  else
    # middle is empty, just insert the heading
    @div_middle.insert_after(@current, temp)
  end
  
  @current = temp
  raise "Block argument is mandatory" unless block_given?
  text = block.call()
  @current.text = text
  return @current
end

#highlight(regex, color = "y", el = @current) ⇒ Fixnum

Scans all REXML::Text children of an REXML::Element for any occurrences of regex. The text will be matched as one, not line by line as you might think. If you want to write a regexp matching multiple lines keep in mind that the dot “.” by default doesn’t match newline characters. Consider using the “m” option (e.g. /regex/m ) which makes dot match newlines or match newlines explicitly. highlight then puts a <span> </span> tag around all matches of regex

Parameters:

  • regex (Regexp)

    a regular expression that will be matched

  • color (String) (defaults to: "y")

    at this point one of “y”, “r”, “g”, “b” (yellow, red, green, blue) is supported

  • el (REXML::Element) (defaults to: @current)

    the Element (scope) which will be searched for pattern matches

Returns:

  • (Fixnum)

    the number of highlighted captures



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/xhtml_report_generator/custom.rb', line 176

def highlight(regex, color="y", el = @current)
  # get all children of the current node
  arr = el.to_a()
  num_matches = 0
  #puts arr.inspect
  # depth first recursion into grand-children
  for i in arr do
    # detach from current
    i.parent = nil
    #puts i.class.to_s()
    if i.class.to_s()  == "REXML::Text"
      # in general a text looks as follows:
      # .*(matchstring|.*)*

      # We get an array of [[start,length], [start,length], ...] for all our regex matches
      positions = i.value().enum_for(:scan, regex).map {
        [Regexp.last_match.begin(0),
          Regexp.last_match.end(0)-Regexp.last_match.begin(0)]
      }
      num_matches += positions.length
      replace_text_with_elements(el, i, "span", {"class" => color}, positions)
    else
      # for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
      # puts "recurse"
      num_matches += highlight(regex, color, i)
      el.add(i)
    end # if  i.class.to_s()  == "REXML::Text"
  end # for i in arr do
  return num_matches
end

#highlight_captures(regex, color = "y", el = @current) ⇒ Fixnum

Scans all REXML::Text children of an REXML::Element for any occurrences of regex. The text will be matched as one, not line by line as you might think. If you want to write a regexp matching multiple lines keep in mind that the dot “.” by default doesn’t match newline characters. Consider using the “m” option (e.g. /regex/m ) which makes dot match newlines or match newlines explicitly. highlight_captures then puts a <span> </span> tag around all captures of the regex NOTE: nested captures are not supported and don’t make sense in this context!!

Parameters:

  • regex (Regexp)

    a regular expression that will be matched

  • color (String) (defaults to: "y")

    at this point one of “y”, “r”, “g”, “b” (yellow, red, green, blue) is supported

  • el (REXML::Element) (defaults to: @current)

    the Element (scope) which will be searched for pattern matches, by default the last inserted element will be scanned

Returns:

  • (Fixnum)

    the number of highlighted captures



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/xhtml_report_generator/custom.rb', line 132

def highlight_captures(regex, color="y", el = @current)
  # get all children of the current node
  arr = el.to_a()
  num_matches = 0
  # depth first recursion into grand-children
  for i in arr do
    # detach from current
    i.parent = nil
    if i.class.to_s()  == "REXML::Text"
      # in general a text looks as follows:
      # .*(matchstring|.*)*

      # We get an array of [[start,length], [start,length], ...] for all our regex SUB-matches
      positions = i.value().enum_for(:scan, regex).flat_map {
        # Regexp.last_match is a MatchData object, the index 0 is the entire match, 1 to n are captures
        array = Array.new
        for k in 1..Regexp.last_match.length - 1 do
          array.push([Regexp.last_match.begin(k),
            Regexp.last_match.end(k)-Regexp.last_match.begin(k)])
        end
        # return the array for the flat_map
        array
      }
      num_matches += positions.length
      replace_text_with_elements(el, i, "span", {"class" => color}, positions)
    else
      # for non-text nodes we recurse into it and finally reattach to our parent to preserve ordering
      num_matches += highlight_captures(regex, color, i)
      el.add(i)
    end # if  i.class.to_s()  == "REXML::Text"
  end # for i in arr do
  return num_matches
end

#html(text) ⇒ REXML::Element

insert arbitrary xml code after the @current element in the content pane (div middle)

Parameters:

  • text (String)

    valid xhtml code which is included into the document

Returns:

  • (REXML::Element)

    the Element which was just added



110
111
112
113
114
115
116
117
118
119
# File 'lib/xhtml_report_generator/custom.rb', line 110

def html(text)
  # we need to create a new document with a pseudo root
  doc = REXML::Document.new("<root>"+text+"</root>")
  # then we move all children of root to the actual div middle element and insert after current
  for i in doc.root.to_a do
    @div_middle.insert_after(@current, i)
    @current = i
  end
  return @current
end

#replace_text_with_elements(parent, element, tagname, attribs, index_length_array) ⇒ Object

Helper Method for the highlight methods. it will introduce specific xhtml tags around parts of a text child of an xml element.

Examples:

we have the following xml part
<test>
  some arbitrary
  text child content
</test>
now we call replace_text_with_elements to place <span> around the word "arbitrary" 
=>
<test>
  some <span>arbitrary</span>
  text child content
</test>

Parameters:

  • element (REXML::Element)

    the element in whose text tags will be added at the specified indices of @index_length_array

  • parent (REXML::Element)

    the parent to which @element should be attached after parsing

  • tagname (String)

    the tag that will be introduced as <tagname> at the indices specified

  • attribs (Hash)

    Attributes that will be added to the inserted tag e.g. <tagname attrib=“test”>

  • index_length_array (Array)

    Array of the form [[index, lenght], [index, lenght], …] that specifies the start position and length of the substring around which the tags will be introduced



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/xhtml_report_generator/custom.rb', line 304

def replace_text_with_elements(parent, element, tagname, attribs, index_length_array)
  last_end = 0
  index = 0
  #puts index_length_array.inspect
  #puts element.inspect
  for j in index_length_array do
    # reattach normal (unmatched) text
    if j[0] > last_end
      text = REXML::Text.new(element.value()[ last_end, j[0] - last_end ])
      parent.add_text(text)
    end
    #create the tag node with attributes and add the text to it
    tag = parent.add_element(REXML::Element.new(tagname), attribs)
    tag.add_text(element.value()[ j[0], j[1] ])
    last_end = j[0]+j[1]

    # in the last round check for any remaining text
    if index == index_length_array.length - 1
      if last_end < element.value().length
        text = REXML::Text.new(element.value()[ last_end, element.value().length - last_end ])
        parent.add(text)
      end
    end
    index  += 1
  end # for j in positions do

  # don't forget to reattach the textnode if there are no regex matches at all
  if index == 0
    parent.add(element)
  end

end

#set_current!(xpath) ⇒ Object

set the current element to the element or first element matched by the xpath expression. The current element is the one which can be modified through highlighting.

Parameters:

  • xpath (REXML::Element|String)

    the element or an xpath string



61
62
63
64
65
66
67
68
69
# File 'lib/xhtml_report_generator/custom.rb', line 61

def set_current!(xpath)
  if xpath.is_a?(REXML::Element)
    @current = xpath
  elsif xpath.is_a?(String)
    @current = @document.elements[xpath]
  else 
    raise "xpath is neither a String nor a REXML::Element"
  end
end

#set_title(title) ⇒ Object

sets the title of the document in the <head> section as well as in the layout header div

create_layout must be called before!

Parameters:

  • title (String)

    the text which will be insertead



41
42
43
44
45
46
47
48
49
# File 'lib/xhtml_report_generator/custom.rb', line 41

def set_title(title)
  if !@layout
    raise "call create_layout first"
  end
  pagetitle = @document.elements["//head/title"]
  pagetitle.text = title
  div = @document.elements["//body/div[@class='head']"]
  div.text = title
end

#table(table_data, headers = 0, table_attrs = {}, tr_attrs = {}, th_attrs = {}, td_attrs = {}) ⇒ REXML::Element

creates a html table from two dimensional array of the form Array [row] [col]

Parameters:

  • table_data (Array<Array>)

    of the form Array [row] [col] containing all data, the ‘.to_s’ method will be called on each element,

  • headers (Number) (defaults to: 0)

    either of 0, 1, 2, 3. Where 0 is no headers (<th>) at all, 1 is only the first row, 2 is only the first column and 3 is both, first row and first column as <th> elements. Every other number is equivalent to the bitwise AND of the two least significant bits with 1, 2 or 3

Returns:

  • (REXML::Element)

    the Element which was just added



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/xhtml_report_generator/custom.rb', line 213

def table(table_data, headers=0, table_attrs={}, tr_attrs={}, th_attrs={}, td_attrs={})

  temp = REXML::Element.new("table")
  temp.add_attributes(table_attrs)

  for i in 0..table_data.length-1 do
    row = temp.add_element("tr", tr_attrs)
    for j in 0..table_data[i].length-1 do
      if (i == 0 && (0x1 & headers)==0x1)
        col = row.add_element("th", th_attrs)
      elsif (j == 0 && (0x2 & headers)==0x2)
        col = row.add_element("th", th_attrs)
      elsif ((i == 0 || j ==0) && (0x3 & headers)==0x3)
        col = row.add_element("th", th_attrs)
      else
        col = row.add_element("td", td_attrs)
      end
      col.add_text(table_data[i][j].to_s)
    end
  end

  @div_middle.insert_after(@current, temp)
  @current = temp
  return @current
end