Class NWN::TwoDA::Table

  1. lib/nwn/twoda.rb
Parent: Object

Methods

public class

  1. new
  2. parse
  3. read_from

public instance

  1. []
  2. []=
  3. by_col
  4. by_row
  5. column_name_to_id
  6. parse
  7. to_2da
  8. write_to

Constants

CELL_PAD_SPACES = 4

Attributes

columns [RW] An array of all column names present in this 2da table.
newline [RW] What to use to set up newlines. Alternatively, specify the environ variable NWN_LIB_2DA_NEWLINE with one of the following:
0 for windows newlines: \r\n
1 for unix newlines: \n
2 for caret return only: \r

defaults to r\n.

rows [RW] An array of row arrays, without headers.

Public class methods

new ()

Create a new, empty 2da table.

[show source]
    # File lib/nwn/twoda.rb, line 76
76:       def initialize
77:         @columns = []
78:         @rows = []
79:         @newline = "\r\n"
80:       end
parse (bytes)

Parse a existing string containing a full 2da table. Returns a TwoDA::Table.

[show source]
     # File lib/nwn/twoda.rb, line 96
 96:       def self.parse bytes
 97:         obj = self.new
 98:         obj.parse bytes
 99:         obj
100:       end
read_from (io)

Creates a new Table object from a given IO source.

file
A IO object pointing to a 2da file.
[show source]
    # File lib/nwn/twoda.rb, line 85
85:       def self.read_from io
86:         self.parse io.read()
87:       end

Public instance methods

[] (row, column = nil)

Alias for by_row

[]= (row, column = nil, value = nil)

Set a cell or row value.

row
The row to operate on (starts at 0)
column
Optional column name or index.
value
New value, either a full row, or a single value.

Examples:

TwoDA.get('portraits')[1, "BaseResRef"] = "hi"
TwoDA.get('portraits')[1] = %w{1 2 3 4 5 6}
[show source]
     # File lib/nwn/twoda.rb, line 201
201:       def []= row, column = nil, value = nil
202:         if value.nil?
203:           value = column
204:           raise ArgumentError, "Expected array for setting a whole row" unless value.is_a?(Array)
205:         end
206: 
207:         if value.is_a?(Array)
208:           raise ArgumentError, "Given array size does not match table columns (got: #{value.size}, want: #{self.columns.size})" unless value.size == self.columns.size
209:           new_row = Row.new
210:           new_row.concat(value.map {|x| x.to_s})
211: 
212:           @rows[row] = new_row
213: 
214:         else
215:           col = column_name_to_id column
216:           @rows[row][col] = value
217: 
218:         end
219:       end
by_col (column, row = nil)

Retrieve data by column.

column
The column to retrieve (name or id).
row
The row to retrieve (starts at 0), or nil for all rows.
[show source]
     # File lib/nwn/twoda.rb, line 226
226:       def by_col column, row = nil
227:         column = column_name_to_id column
228:         raise ArgumentError, "column must not be nil." if column.nil?
229:         row.nil? ? @rows.map {|v| v[column] } : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column])
230:       end
by_row (row, column = nil)

Retrieve data by row.

row
The row to retrieve (starts at 0)
column
The column to retrieve (name or id), or nil for all columns.
[show source]
     # File lib/nwn/twoda.rb, line 185
185:       def by_row row, column = nil
186:         column = column_name_to_id column
187:         column.nil? ? @rows[row.to_i] : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column])
188:       end
column_name_to_id (column)

Translate a column name to its array offset; will validate and raise an ArgumentError if the given argument is invalid or the column cannot be resolved.

[show source]
     # File lib/nwn/twoda.rb, line 236
236:       def column_name_to_id column
237:          case column
238:           when String
239:             @columns.index(column) or raise ArgumentError, "Not a valid column name: #{column}"
240:           when Fixnum
241:             column
242:           when NilClass
243:             nil
244:           else
245:             raise ArgumentError, "Invalid column type: #{column} as #{column.class}"
246:         end
247:       end
parse (bytes)

Parses a string that represents a valid 2da definition. Replaces any content this table may already have. This will cope with all misformatting in the same way that NWN1 itself does. NWN2 employs slightly different parsing rules, and may or may not be compatible in the fringe cases.

Will raise an ArgumentError if the given bytes do not contain a valid 2DA header, or the file is so badly misshaped that it will not ever be parsed correctly by NWN1.

[show source]
     # File lib/nwn/twoda.rb, line 112
112:       def parse bytes
113:         magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip }
114: 
115:         raise ArgumentError, "Not valid 2da: No valid header found (got: #{magic[0,20].inspect}..)" if
116:           magic !~ /^2DA\s+V2.0$/
117: 
118:         # strip all empty lines; they are regarded as comments
119:         data.reject! {|ln| ln.strip == ""}
120: 
121:         header = data.shift
122: 
123:         header = Shellwords.shellwords(header.strip)
124:         data.map! {|line|
125:           Shellwords.shellwords(line.strip)
126:         }
127: 
128:         new_row_data = []
129: 
130:         id_offset = 0
131:         idx_offset = 0
132:         data.each_with_index {|row, idx|
133:           id = row.shift
134: 
135:           NWN.log_debug "Warning: invalid ID in line #{idx}: #{id.inspect}" if id !~ /^\d+$/
136: 
137:           id = id.to_i + id_offset
138: 
139:           # Its an empty row - NWN strictly numbers by counted lines - then so do we.
140:           while id > idx + idx_offset
141:             NWN.log_debug "Warning: missing ID at #{id - id_offset}, fixing that for you."
142:             idx_offset += 1
143:           end
144: 
145:           # NWN automatically increments duplicate IDs - so do we.
146:           while id < idx + idx_offset
147:             NWN.log_debug "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you."
148:             id_offset += 1
149:             id += 1
150:           end
151: 
152:           # NWN fills in missing columns with an empty value - so do we.
153:           NWN.log_debug "Warning: row #{id} (real: #{id - id_offset}) misses " +
154:             "#{header.size - row.size} columns at the end, fixed" if
155:               row.size < header.size
156: 
157:           row << "" while row.size < header.size
158: 
159:           new_row_data << k_row = Row.new(row)
160:           k_row.table = self
161: 
162:           k_row.map! {|cell|
163:             cell = case cell
164:               when nil; raise "Bug in parser: nil-value for cell"
165:               when "****"; ""
166:               else cell
167:             end
168:           }
169: 
170:           NWN.log_debug "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
171:             k_row.size > header.size
172: 
173:           k_row.pop while k_row.size > header.size
174:         }
175: 
176:         @columns = header
177:         @rows = new_row_data
178:       end
to_2da ()

Returns this table as a valid 2da to be written to a file.

[show source]
     # File lib/nwn/twoda.rb, line 250
250:       def to_2da
251:         ret = []
252: 
253:         # Contains the maximum string length by each column,
254:         # from which we can calulate the padding we need that
255:         # things align properly.
256:         id_cell_size = @rows.size.to_s.size + CELL_PAD_SPACES
257:         max_cell_size_by_column = @columns.map {|col|
258:           ([col] + by_col(col)).inject(0) {|max, cell|
259:             cell = '"%s"' % cell if cell =~ /\s/
260:             cell.to_s.size > max ? cell.to_s.size : max
261:           } + CELL_PAD_SPACES
262:         }
263: 
264:         ret << "2DA V2.0"
265:         ret << ""
266: 
267:         rv = []
268:         rv << " " * id_cell_size
269:         @columns.each_with_index {|column, column_idx|
270:           rv << column + " " * (max_cell_size_by_column[column_idx] - column.size)
271:         }
272:         ret << rv.join("").rstrip
273: 
274:         @rows.each_with_index {|row, row_idx|
275:           rv = []
276:           rv << row_idx.to_s + " " * (id_cell_size - row_idx.to_s.size)
277:           row.each_with_index {|cell, column_idx|
278:             cell = "****" if cell == ""
279:             cell = '"%s"' % cell if cell =~ /\s/
280:             rv << cell + " " * (max_cell_size_by_column[column_idx] - cell.size)
281:           }
282:           ret << rv.join("").rstrip
283:         }
284: 
285:         # Append an empty newline.
286:         ret << ""
287: 
288:         ret.join(case NWN.setting("2da_newline")
289:           when "0", false
290:             "\r\n"
291:           when "1"
292:             "\n"
293:           when "2"
294:             "\r"
295:           when nil
296:             @newline
297:         end)
298:       end
write_to (io)

Dump this table to a IO object.

[show source]
    # File lib/nwn/twoda.rb, line 90
90:       def write_to io
91:         io.write(self.to_2da)
92:       end