class TaskJuggler::CSVFile

This is a very lightweight version of the Ruby library class CSV. That class changed significantly from 1.8 to 1.9 and is a compatibility nightmare. Hence we use our own class.

Attributes

data[R]

Public Class Methods

new(data = nil, separator = ';', quote = '"') click to toggle source

At construction time you need to specify the data container. This is an Array of Arrays that holds the table. Optionally, you can specify a separator and a quote string for the CSV file.

# File lib/taskjuggler/reports/CSVFile.rb, line 27
def initialize(data = nil, separator = ';', quote = '"')
  @data = data
  if !separator.nil? && '."'.include?(separator)
    raise "Illegal separator: #{separator}"
  end
  @separator = separator
  raise "Illegal quote: #{quote}" if quote == '.'
  @quote = quote
end
strToNative(str) click to toggle source

Utility function that tries to convert a String into a native type that is supported by the CSVFile generator. If no native type is found, the input String str will be returned unmodified. nil is returned as nil.

# File lib/taskjuggler/reports/CSVFile.rb, line 196
def CSVFile.strToNative(str)
  if str.nil?
    nil
  elsif /^[-+]?\d+$/ =~ str
    # field is a Fixnum
    str.to_i
  elsif /^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ =~ str
    # field is a Float
    str.to_f
  else
    # Everything else is kept as String
    str
  end
end

Public Instance Methods

parse(str) click to toggle source

Read the data as Array of Arrays from a CSV formated String str.

# File lib/taskjuggler/reports/CSVFile.rb, line 88
def parse(str)
  @data = []
  state = :startOfRecord
  fields = field = quoted = nil

  # Make sure the input is terminated with a record end.
  str += "\n" unless str[-1] == ?\n

  # If the user hasn't defined a separator, we try to detect it.
  @separator = detectSeparator(str) unless @separator

  line = 1
  str.each_utf8_char do |c|
    #puts "c: #{c}  State: #{state}"
    case state
    when :startOfRecord
      # This will store the fields of a record
      fields = []
      state = :startOfField
      redo
    when :startOfField
      field = ''
      quoted = false
      if c == @quote
        # We've found the start of a quoted field.
        state = :inQuotedField
        quoted = true
      elsif c == @separator || c == "\n"
        # We've found an empty field
        field = nil
        state = :fieldEnd
        redo
      else
        # We've found the first character of an unquoted field
        field << c
        state = :inUnquotedField
      end
    when :inQuotedField
      # We are processing the content of a quoted field
      if c == @quote
        # This could be then end of the field or a quoted quote.
        state = :quoteInQuotedField
      else
        # We've found a normal character of the quoted field
        field << c
        line += 1 if c == "\n"
      end
    when :quoteInQuotedField
      # We are processing a quoted quote or the end of a quoted field
      if c == @quote
        # We've found a quoted quote
        field << c
        state = :inQuotedField
      elsif c == @separator || c == "\n"
        state = :fieldEnd
        redo
      else
        raise "Line #{line}: Unexpected character #{c} in cell: #{field}"
      end
    when :inUnquotedField
      # We are processing an unquoted field
      if c == @separator || c == "\n"
        # We've found the end of a unquoted field
        state = :fieldEnd
        redo
      else
        # A normal character of an unquoted field
        field << c
      end
    when :fieldEnd
      # We've completed processing a field. Add the field to the list of
      # fields. Convert Fixnums and Floats in native types.
      fields << unMarshal(field, quoted)

      if c == "\n"
        # The field end is an end of a record as well.
        state = :recordEnd
        redo
      else
        # Get the next field.
        state = :startOfField
      end
    when :recordEnd
      # We've found the end of a record. Add fields to the @data
      # structure.
      @data << fields
      # Look for a new record.
      state = :startOfRecord
      line += 1
    else
      raise "Unknown state #{state}"
    end
  end

  unless state == :startOfRecord
    if state == :inQuotedField
      raise "Line #{line}: Unterminated quoted cell: #{field}"
    else
      raise "Line #{line}: CSV error in state #{state}: #{field}"
    end
  end

  @data
end
read(fileName) click to toggle source

Read the data as Array of Arrays from a CSV formated file fileName. In case '.' is used for the fileName the data is read from $stdin.

# File lib/taskjuggler/reports/CSVFile.rb, line 53
def read(fileName)
  if (fileName == '.')
    file = $stdin
  else
    file = File.open(fileName, 'r')
  end

  parse(file.read)

  file.close unless fileName == '.'
  @data
end
to_s() click to toggle source

Convert the CSV data into a CSV formatted String.

# File lib/taskjuggler/reports/CSVFile.rb, line 67
def to_s
  raise "No seperator defined." if @separator.nil?

  s = ''
  @data.each do |line|
    first = true
    line.each do |field|
      # Don't output a separator before the first field of the line.
       if first
         first = false
       else
         s << @separator
       end
       s << marshal(field)
    end
    s << "\n"
  end
  s
end
write(fileName) click to toggle source

Use this function to write the table into a CSV file fileName. '.' can be used to write to $stdout.

# File lib/taskjuggler/reports/CSVFile.rb, line 39
def write(fileName)
  if (fileName == '.')
    file = $stdout
  else
    file = File.open(fileName, 'w')
  end

  file.write(to_s)

  file.close unless fileName == '.'
end