Class: XhtmlReportGenerator::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/xhtml_report_generator.rb

Overview

This is the main generator class. It can be instanced with custom javascript, css, and ruby files to allow generation of arbitrary reports.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#documentREXML::Document

This is the html document / actual report

Returns:

  • (REXML::Document)

    the current value of document



17
18
19
# File 'lib/xhtml_report_generator.rb', line 17

def document
  @document
end

#fileString

path to the file where this report is saved to. Default: nil

Returns:

  • (String)

    the current value of file



17
18
19
# File 'lib/xhtml_report_generator.rb', line 17

def file
  @file
end

#syncBoolean

if true, the report will be written to disk after every modificaiton. Default: false Note that sync = true can have severe performance impact, since the complete html file is written to disk after each change.

Returns:

  • (Boolean)

    the current value of sync



17
18
19
# File 'lib/xhtml_report_generator.rb', line 17

def sync
  @sync
end

Class Method Details

.create_xhtml_document(title) ⇒ Object

Creates a minimal valid xhtml document including header title and body elements

Parameters:

  • title (String)

    Title in the header section



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/xhtml_report_generator.rb', line 133

def self.create_xhtml_document(title)
  # don't use version 1.1 - firefox has not yet a parser for xml 1.1
  # https://bugzilla.mozilla.org/show_bug.cgi?id=233154
  header = '<?xml version="1.0" encoding="UTF-8"?>'
  # change of doctype to <!DOCTYPE html> for html5 compatibility
  header << '<!DOCTYPE html>'

  doc = REXML::Document.new(header)
  html = doc.add_element("html", {"xmlns" => "http://www.w3.org/1999/xhtml"})
  # create header
  head = html.add_element("head")
  t = head.add_element("title")
  t.text = title
  html.add_element("body")
  return doc
end

Instance Method Details

#cdata(str, parent_element) ⇒ REXML::Element

Surrounds CData tag with c-style comments to remain compatible with normal html. For plain xhtml documents this is not needed. Example //

Parameters:

  • str (String)

    the string to be enclosed in cdata

  • parent_element (REXML::Element)

    the element to which cdata should be added

Returns:

  • (REXML::Element)

    parent_element



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/xhtml_report_generator.rb', line 102

def cdata(str, parent_element)
  # sometimes depending on LC_CTYPE environemnt variable it can happen that js / css files are interpreted
  # with wrong encoding
  str = encoding_fixer(str)
  # somehow there is a problem with CDATA, any text added after will automatically go into the CDATA
  # so we have do add a dummy node after the CDATA and then add the text.
  parent_element.add_text("/*")
  parent_element.add(REXML::CData.new("*/\n"+str+"\n/*"))
  parent_element.add(REXML::Comment.new("dummy comment to make c-style comments for cdata work"))
  parent_element.add_text("*/")
end

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

Appends a

 node after the @current node

Parameters:

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

    attributes for the

     element. The following classes can be passed as attributes and are predefined with a different
    background for your convenience !{"class" => "code0"} (light-blue), !{"class" => "code1"} (red-brown),
    !{"class" => "code2"} (light-green), !{"class" => "code3"} (light-yellow). You may also specify your own background
    as follows: !{"style" => "background: #FF00FF;"}.

Yield Returns:

  • (String)

    the text to be added to the

     element

Returns:

  • (REXML::Element)

    the Element which was just added



316
317
318
319
320
321
322
323
324
325
326
# File 'lib/xhtml_report_generator.rb', line 316

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 = encoding_fixer(block.call())
  @current.add_text(text)
  document_changed()
  return @current
end

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

Appends a

node after the @current node

Parameters:

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

    attributes for the

    element

Yield Returns:

  • (String)

    the text to be added to the

    element

Returns:

  • (REXML::Element)

    the Element which was just added



364
365
366
367
368
369
370
371
372
373
374
# File 'lib/xhtml_report_generator.rb', line 364

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 = encoding_fixer(block.call())
  @current.add_text(text)
  document_changed()
  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[@id='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.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/xhtml_report_generator.rb', line 197

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
    head = @body.add_element("div", {"class" => "head", "id" => "head"})
    head.add_element("button", {"id" => "pre_toggle_linewrap"}).add_text("Toggle Linewrap")

    if (layout & 0x1) != 0
    div = @body.add_element("div", {"class" => "lefttoc split split-horizontal", "id" => "ltoc"})
    div.add_text("Table of Contents")
    div.add_element("br")
    end

    @div_middle = @body.add_element("div", {"class" => "middle split split-horizontal", "id" => "middle"})

    if (layout & 0x2) != 0
    div = @body.add_element("div", {"class" => "righttoc split split-horizontal", "id" => "rtoc"})
    div.add_text("Quick Links")
    div.add_element("br");div.add_element("br")
    end

    @body.add_element("p", {"class" => "#{layout}", "id" => "layout"}).add_text("this text should be hidden")

    @layout = true
  end
  @current = @document.elements["//body/div[@id='middle']"]
  set_title(title)
  document_changed()
end

#custom_table(table_data, opts = {}) ⇒ REXML::Element

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

Examples:

Example of the :special attributes

opts[:special] = [
  {
    col_title: 'rx_DroppedFrameCount', # string or regexp or nil  # if neither title nor index are present, the condition is evaluated for all <td> cells
    col_index: 5..7,  # Fixnum, Range or nil     # index has precedence over title
    row_title: 'D_0_BE_iMix',  # string or regexp or nil
    row_index: 6,  # Fixnum, Range or nil
    condition: Proc.new { |e| Integer(e) != 0 },   # a proc
    attributes: {"style" => "background-color: #DB7093;"},
  },
]

Parameters:

  • table_data (Array<Array>)

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

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

    a customizable set of options

Options Hash (opts):

  • :headers (Number)

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

  • :data_is_xhtml (Boolean)

    defaults to false, if true table_data is inserted as xhtml without any sanitation or escaping. This way a table can be used for custom layouts.

  • :table_attrs (Hash)

    html attributes for the

    tag

  • :th_attrs (Hash)

    html attributes for the

  • tag

  • :td_attrs (Hash)

    html attributes for the

  • tag

  • :tr_attrs (Hash)

    html attributes for the

  • tag

  • :special (Array<Hash>)

    Array of hashes for custom attributes on specific cells (

  • only) of the table

    Returns:

    • (REXML::Element)

      the Element which was just added

    
    
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    # File 'lib/xhtml_report_generator.rb', line 604
    
    def custom_table(table_data, opts = {})
      defaults = {
        headers: 0,
        data_is_xhtml: false,
        table_attrs: {},
        th_attrs: {},
        tr_attrs: {},
        td_attrs: {},
        special: [],
      }
      o = defaults.merge(opts)
    
      temp = REXML::Element.new("table")
      temp.add_attributes(o[:table_attrs])
      row_titles = table_data.collect{|row| row[0].to_s}
      col_titles = table_data[0].collect{|title| title.to_s}
    
      for i in 0..table_data.length-1 do # row
        row = temp.add_element("tr", o[:tr_attrs])
        for j in 0..table_data[i].length-1 do # column
          if (i == 0 && (0x1 & o[:headers])==0x1)
            col = row.add_element("th", o[:th_attrs])
          elsif (j == 0 && (0x2 & o[:headers])==0x2)
            col = row.add_element("th", o[:th_attrs])
          elsif ((i == 0 || j ==0) && (0x3 & o[:headers])==0x3)
            col = row.add_element("th", o[:th_attrs])
          else
            # we need to deepcopy the attributes
            _td_attrs = Marshal.load(Marshal.dump(o[:td_attrs]))
    
            # check all special criteria
            o[:special].each do |h|
              # check if the current cell is a candidate for special
              if !h[:col_index].nil?
                if h[:col_index].is_a?(Range)
                  next if (!h[:col_index].include?(j)) # skip if not in range
                elsif h[:col_index].is_a?(Integer)
                  next if (h[:col_index] != j)         # skip if not at index
                end
              elsif !h[:col_title].nil?
                next if !col_titles[j].match(h[:col_title])
              end
              # check if the current cell is a candidate for special
              if !h[:row_index].nil?
                if h[:row_index].is_a?(Range)
                  next if (!h[:row_index].include?(i)) # skip if not in range
                elsif h[:row_index].is_a?(Integer)
                  next if (h[:row_index] != i)         # skip if not at index
                end
              elsif !h[:row_title].nil?
                next if !row_titles[i].match(h[:row_title])
              end
    
              # here we are a candidate for special, so we check if we meet the condition
              # puts h[:attributes].inspect
              # puts "cell value row #{i} col #{j}: #{table_data[i][j]}"
              # puts h[:condition].call(table_data[i][j]).inspect
              if h[:condition].call(table_data[i][j])
                h[:attributes].each { |attr, val|
                  # debug, verify deepcopy
                  # puts "objects are equal:  #{_td_attrs[attr].equal?(o[:td_attrs][attr])}"
                  if !_td_attrs[attr].nil?
                    # assume the existing attribute is a string (other types don't make much sense for html)
                    _td_attrs[attr] << val
                  else
                    # create the attribute if it is not already there
                    _td_attrs[attr] = val
                  end
                }
              end
            end
    
            col = row.add_element("td", _td_attrs)
          end
          if o[:data_is_xhtml]
            # we need to create a new document with a pseudo root because having multiple nodes at top
            # level is not valid xml
            doc = REXML::Document.new("<root>" + table_data[i][j].to_s + "</root>")
            # then we move all children of root to the actual div middle element and insert after current
            for elem in doc.root.to_a do
              if elem.is_a?(REXML::Text)
                # due to reasons unclear to me, text needs special treatment
                col.add_text(elem)
              else
                col.add_element(elem) # add the td element
              end
            end
          else
            col.add_text(table_data[i][j].to_s)
          end
        end # for j in 0..table_data[i].length-1 do # column
      end # for i in 0..table_data.length-1 do # row
    
      @div_middle.insert_after(@current, temp)
      @current = temp
      document_changed()
      return @current
    end
    

    #document_changedObject

    This method should be called after every change to the document. Here we ensure the report is written to disk after each change if #sync is true. If #sync is false this method does nothing

    
    
    182
    183
    184
    185
    186
    187
    188
    189
    # File 'lib/xhtml_report_generator.rb', line 182
    
    def document_changed()
      if @sync
        if @file.nil?
          raise "You must call #write at least once before you can enable synced mode"
        end
        write()
      end
    end
    

    #element_to_string(elem) ⇒ String

    Parameters:

    • elem (REXML::Element)

    Returns:

    • (String)
    
    
    289
    290
    291
    292
    293
    294
    # File 'lib/xhtml_report_generator.rb', line 289
    
    def element_to_string(elem)
      f = REXML::Formatters::Transitive.new(0) # use Transitive to preserve source formatting (e.g. <pre> tags)
      out = ""
      f.write(elem, out)
      return out
    end
    

    #encoding_fixer(str) ⇒ String

    Check if the give string is a valid UTF-8 byte sequence. If it is not valid UTF-8, then all invalid bytes are replaced by "\u2e2e" (\xe2\xb8\xae) ('REVERSED QUESTION MARK') because the default replacement character "\uFFFD" ('QUESTION MARK IN DIAMOND BOX') is two slots wide and might destroy mono spaced formatting

    Parameters:

    • str (String)

      of any encoding

    Returns:

    • (String)

      UTF-8 encoded valid string

    
    
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    # File 'lib/xhtml_report_generator.rb', line 120
    
    def encoding_fixer(str)
      str = str.to_s  # catch str = nil
      #if !str.force_encoding('UTF-8').valid_encoding?
      #  str.encode!('UTF-8', 'ISO-8859-1', {:invalid => :replace, :undef => :replace, :xml => :text})
      #end
      tmp = str.force_encoding('UTF-8').encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => "\u2e2e")
      # replace all special control chars as well but keep newline and whitespace "\u2e2e"
      tmp.force_encoding('binary').gsub!(/[\x00-\x07\x0C-\x1F]|\xef\xbf\xbe|\xef\xbf\xbf/n, "\xe2\xb8\xae".force_encoding('binary'))
      return tmp.force_encoding('UTF-8')
    end
    

    #get_code_html(attrs = {}, &block) ⇒ String

    Instead of adding content to the report, this method returns the produced html code as a string. This can be used to insert code into #custom_table (with the option data_is_xhtml: true)

    Returns:

    • (String)

      the code including

       tags as a string

    See Also:

    
    
    300
    301
    302
    303
    304
    305
    306
    307
    # File 'lib/xhtml_report_generator.rb', line 300
    
    def get_code_html(attrs={}, &block)
      temp = REXML::Element.new("pre")
      temp.add_attributes(attrs)
      raise "Block argument is mandatory" unless block_given?
      text = encoding_fixer(block.call())
      temp.add_text(text)
      element_to_string(temp)
    end
    

    #get_content_html(attrs = {}, &block) ⇒ String

    Instead of adding content to the report, this method returns the produced html code as a string. This can be used to insert code into #custom_table (with the option data_is_xhtml: true)

    Returns:

    • (String)

      the code including

       tags as a string

    See Also:

    
    
    351
    352
    353
    354
    355
    356
    357
    358
    # File 'lib/xhtml_report_generator.rb', line 351
    
    def get_content_html(attrs={}, &block)
      temp = REXML::Element.new("p")
      temp.add_attributes(attrs)
      raise "Block argument is mandatory" unless block_given?
      text = encoding_fixer(block.call())
      temp.add_text(text)
      element_to_string(temp)
    end
    

    #get_currentREXML::Element

    returns the current xml element

    Returns:

    • (REXML::Element)

      the xml element after which the following elements will be added

    
    
    265
    266
    267
    # File 'lib/xhtml_report_generator.rb', line 265
    
    def get_current()
      return @current
    end
    

    #get_element_text(el = @current, recursive = true) ⇒ String

    returns the plain text without any xml tags of the specified element and all its children

    Parameters:

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

      The element from which to fetch the text children. Defaults to @current

    • recursive (Boolean) (defaults to: true)

      whether or not to recurse into the children of the given "el"

    Returns:

    • (String)

      text contents of xml node

    
    
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    # File 'lib/xhtml_report_generator.rb', line 273
    
    def get_element_text(el = @current, recursive = true)
      out = ""
      el.to_a.each { |child|
        if child.is_a?(REXML::Text)
          out << child.value()
        else
          if recursive
            out << get_element_text(child, true)
          end
        end
      }
      return out
    end
    

    #get_image_html(path, attributes = {}) ⇒ String

    Instead of adding content to the report, this method returns the produced html code as a string. This can be used to insert code into #custom_table (with the option data_is_xhtml: true)

    Returns:

    • (String)

      the code including

       tags as a string

    See Also:

    
    
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    # File 'lib/xhtml_report_generator.rb', line 428
    
    def get_image_html(path, attributes = {})
      # read image as binary and do a base64 encoding
      binary_data = Base64.strict_encode64(IO.binread(path))
      type = File.extname(path).gsub('.', '')
      # create the element
      temp = REXML::Element.new("img")
      # add the picture
      temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
      temp.add_attributes(attributes)
      element_to_string(temp)
    end
    

    Instead of adding content to the report, this method returns the produced html code as a string. This can be used to insert code into #custom_table (with the option data_is_xhtml: true)

    Returns:

    See Also:

    
    
    396
    397
    398
    399
    400
    401
    402
    403
    404
    # File 'lib/xhtml_report_generator.rb', line 396
    
    def get_link_html(href, attrs={}, &block)
      temp = REXML::Element.new("a")
      attrs.merge!({"href" => href})
      temp.add_attributes(attrs)
      raise "Block argument is mandatory" unless block_given?
      text = encoding_fixer(block.call())
      temp.add_text(text)
      element_to_string(temp)
    end
    

    #get_titleString

    returns the title text of the report

    Returns:

    • (String)

      The title of the report

    
    
    245
    246
    247
    248
    # File 'lib/xhtml_report_generator.rb', line 245
    
    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

    
    
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    # File 'lib/xhtml_report_generator.rb', line 711
    
    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 = encoding_fixer(block.call())
      @current.text = text
      document_changed()
      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

    
    
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    # File 'lib/xhtml_report_generator.rb', line 731
    
    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[@id='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 = encoding_fixer(block.call())
      @current.text = text
      document_changed()
      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 tag around all matches of regex

    Parameters:

    • regex (Regexp)

      a regular expression that will be matched

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

      either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")

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

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

    Returns:

    • (Fixnum)

      the number of highlighted captures

    
    
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    # File 'lib/xhtml_report_generator.rb', line 523
    
    def highlight(regex, color="y", el = @current)
      # get all children of the current node
      arr = el.to_a()
      num_matches = 0
      #puts arr.inspect
      # first we have to detach all children from parent, otherwise we can cause ordering issues
      arr.each {|i| i.remove() }
      # depth first recursion into grand-children
      for i in arr do
        #puts i.class.to_s()
        if i.is_a?(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
          if ["y","r","g","b"].include?(color)
            attr = {"class" => color}
          elsif color.match(/^#[A-Fa-f0-9]{6}$/)
            attr = {"style" => "background: #{color};"}
          else
            raise "invalid color: #{color}"
          end
          replace_text_with_elements(el, i, "span", attr, 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.is_a?(REXML::Text)
      end # for i in arr do
      document_changed()
      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 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")

      either one of "y", "r", "g", "b" (yellow, red, green, blue) or a valid html color code (e.g. "#80BFFF")

    • 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

    
    
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    # File 'lib/xhtml_report_generator.rb', line 471
    
    def highlight_captures(regex, color="y", el = @current)
      # get all children of the current node
      arr = el.to_a()
      num_matches = 0
      # first we have to detach all children from parent, otherwise we can cause ordering issues
      arr.each {|i| i.remove() }
      # depth first recursion into grand-children
      for i in arr do
        if i.is_a?(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
          if ["y","r","g","b"].include?(color)
            attr = {"class" => color}
          elsif color.match(/^#[A-Fa-f0-9]{6}$/)
            attr = {"style" => "background: #{color};"}
          else
            raise "invalid color: #{color}"
          end
          replace_text_with_elements(el, i, "span", attr, 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.is_a?(REXML::Text)
      end # for i in arr do
      document_changed()
      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

    
    
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    # File 'lib/xhtml_report_generator.rb', line 379
    
    def html(text)
      # we need to create a new document with a pseudo root becaus having multiple nodes at top
      # level is not valid xml
      doc = REXML::Document.new("<root>"+text.to_s+"</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
      document_changed()
      return @current
    end
    

    #image(path, attributes = {}) ⇒ Object

    Parameters:

    • path (String)

      absolute or relative path to the image that should be inserted into the report

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

      attributes for the element, any valid html attributes can be specified you may specify attributes such "alt", "height", "width"

    • attrs (Hash)

      a customizable set of options

    
    
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    # File 'lib/xhtml_report_generator.rb', line 444
    
    def image(path, attributes = {})
      # read image as binary and do a base64 encoding
      binary_data = Base64.strict_encode64(IO.binread(path))
      type = File.extname(path).gsub('.', '')
      # create the element
      temp = REXML::Element.new("img")
      # add the picture
      temp.add_attribute("src","data:image/#{type};base64,#{binary_data}")
      temp.add_attributes(attributes)
    
      @div_middle.insert_after(@current, temp)
      @current = temp
      document_changed()
      return @current
    end
    

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

    Appends a

    Appends a <a href = > node after the @current nodes

    
    
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    # File 'lib/xhtml_report_generator.rb', line 411
    
    def link(href, attrs={}, &block)
      temp = REXML::Element.new("a")
      attrs.merge!({"href" => href})
      temp.add_attributes(attrs)
      @div_middle.insert_after(@current, temp)
      @current = temp
      raise "Block argument is mandatory" unless block_given?
      text = encoding_fixer(block.call())
      @current.add_text(text)
      document_changed()
      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:

    • parent (REXML::Element)

      the parent to which "element" should be attached after parsing, e.g.

    • 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 in the example

    • tagname (String)

      the tag that will be introduced as at the indices specified

    • attribs (Hash)

      Attributes that will be added to the inserted tag e.g.

    • 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

    
    
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    # File 'lib/xhtml_report_generator.rb', line 771
    
    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)
          # add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
          parent.add_text( element.value()[ last_end, j[0] - last_end ] )
        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)
            # add text without creating a textnode, textnode screws up formatting (e.g. all whitespace are condensed into one)
            parent.add_text( element.value()[ last_end, element.value().length - last_end ] )
          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

    
    
    253
    254
    255
    256
    257
    258
    259
    260
    261
    # File 'lib/xhtml_report_generator.rb', line 253
    
    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 section as well as in the layout header div create_layout must be called before!

    Parameters:

    • title (String)

      the text which will be insertead

    
    
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    # File 'lib/xhtml_report_generator.rb', line 232
    
    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[@id='head']"]
      div.text = title
      document_changed()
    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 (

    ) at all, 1 is only the first row, 2 is only the first column and 3 is both, first row and first column as 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

    
    
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    # File 'lib/xhtml_report_generator.rb', line 568
    
    def table(table_data, headers=0, table_attrs={}, tr_attrs={}, th_attrs={}, td_attrs={})
      opts = {
        headers: headers,
        data_is_xhtml: false,
        table_attrs: table_attrs,
        th_attrs: th_attrs,
        tr_attrs: tr_attrs,
        td_attrs: td_attrs,
      }
      custom_table(table_data, opts)
    end
    

    #to_s(indent = 0) ⇒ String

    returns the string representation of the xml document

    Parameters:

    • indent (Number) (defaults to: 0)

      indent for child elements. defaults to 0. Note: if you change the indet this might destroy formatting of

       sections

    Returns:

    • (String)

      formatted xml document

    
    
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    # File 'lib/xhtml_report_generator.rb', line 154
    
    def to_s(indent = 0)
      output = ""
      # note :  transitive is needed to preserve newlines in <pre> tags
      # note2:  the hash options syntax is supported only from ruby version >= 2.0.0 we need the old style
      #         for compatibility with 1.9.3
      # @document.write({:output=>output, :indent=>indent, :transitive=>true})
      # change to Formatters since document.write is deprecated
      f = REXML::Formatters::Transitive.new(indent)
      f.write(@document, output)
      return output
    end
    

    #write(file = @file, mode = 'w') ⇒ Object

    Saves the xml document to a file. If no file is given, the file which was used most recently for this Generator object will be overwritten.

    Parameters:

    • file (String) (defaults to: @file)

      absolute or relative path to the file to which will be written. Default: last file used.

    • mode (String) (defaults to: 'w')

      defaults to 'w', one of the file open modes that allows writing ['r+','w','w+','a','a+']

    
    
    170
    171
    172
    173
    174
    175
    176
    177
    # File 'lib/xhtml_report_generator.rb', line 170
    
    def write(file=@file, mode='w')
      # instance variables are nil if they were never initialized
      if file.nil?
        raise "no valid file given: '#{file}'"
      end
      @file = file
      File.open(file, "#{mode}:UTF-8") {|f| f.write(self.to_s.force_encoding(Encoding::UTF_8))}
    end