Class: TaskJuggler::CSVFile

Inherits:
Object show all
Defined in:
lib/taskjuggler/reports/CSVFile.rb

Overview

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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = nil, separator = ';', quote = '"') ⇒ CSVFile

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.



28
29
30
31
32
33
34
35
36
# File 'lib/taskjuggler/reports/CSVFile.rb', line 28

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

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



23
24
25
# File 'lib/taskjuggler/reports/CSVFile.rb', line 23

def data
  @data
end

Class Method Details

.strToNative(str) ⇒ Object

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.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/taskjuggler/reports/CSVFile.rb', line 197

def CSVFile.strToNative(str)
  if str.nil?
    nil
  elsif /^[-+]?\d+$/ =~ str
    # field is an Integer
    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

Instance Method Details

#parse(str) ⇒ Object

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



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/taskjuggler/reports/CSVFile.rb', line 89

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 Integers 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) ⇒ Object

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.



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/taskjuggler/reports/CSVFile.rb', line 54

def read(fileName)
  if (fileName == '.')
    file = $stdin
  else
    file = File.open(fileName, 'r')
  end

  parse(file.read)

  file.close unless fileName == '.'
  @data
end

#to_sObject

Convert the CSV data into a CSV formatted String.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/taskjuggler/reports/CSVFile.rb', line 68

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) ⇒ Object

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



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/taskjuggler/reports/CSVFile.rb', line 40

def write(fileName)
  if (fileName == '.')
    file = $stdout
  else
    file = File.open(fileName, 'w')
  end

  file.write(to_s)

  file.close unless fileName == '.'
end