Class: Xlsxrb::Elements::Worksheet

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxrb/elements/worksheet.rb,
sig/generated/xlsxrb/elements/worksheet.rbs

Overview

Represents a single fully parsed, in-memory worksheet in a workbook. Provides coordinate random access (sheet), row lookups (row_at), and immutable cell updates (update_cell).

Examples:

Access cells and rows

sheet = workbook.sheet(0).load
cell = sheet["A1"]
row = sheet.row_at(0)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, rows: [], columns: [], charts: [], unmapped_data: {}, errors: nil) ⇒ Worksheet

: (name: String, ?rows: Array, ?columns: Array, ?charts: Array[Hash[Symbol, untyped]], ?unmapped_data: Hash[untyped, untyped], ?errors: Array?) -> void

Parameters:

  • name (String)

    The worksheet name (max 31 characters).

  • rows (Array<Elements::Row>) (defaults to: [])

    Rows in the sheet.

  • columns (Array<Elements::Column>) (defaults to: [])

    Column definitions.

  • charts (Array<Hash>) (defaults to: [])

    Charts in the sheet.

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

    Additional metadata for round-tripping.

  • errors (Array<String>, nil) (defaults to: nil)

    Validation errors.

  • name: (String)
  • rows: (Array[Elements::Row]) (defaults to: [])
  • columns: (Array[Elements::Column]) (defaults to: [])
  • charts: (Array[Hash[Symbol, untyped]]) (defaults to: [])
  • unmapped_data: (Hash[untyped, untyped]) (defaults to: {})
  • errors: (Array[String], nil) (defaults to: nil)


29
30
31
32
33
34
35
36
37
# File 'lib/xlsxrb/elements/worksheet.rb', line 29

def initialize(name:, rows: [], columns: [], charts: [], unmapped_data: {}, errors: nil)
  @name = name
  @rows = (rows || []).freeze
  @columns = (columns || []).freeze
  @charts = (charts || []).freeze
  @unmapped_data = (unmapped_data || {}).freeze
  computed_errors = errors || self.class.validate(@name, @rows)
  @errors = computed_errors.freeze
end

Instance Attribute Details

#chartsObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def charts
  @charts
end

#columnsObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def columns
  @columns
end

#errorsObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def errors
  @errors
end

#nameObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def name
  @name
end

#rowsObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def rows
  @rows
end

#unmapped_dataObject (readonly)

Returns:

  • (Object)


20
21
22
# File 'lib/xlsxrb/elements/worksheet.rb', line 20

def unmapped_data
  @unmapped_data
end

Class Method Details

.validate(name, rows) ⇒ Array<String>

Validates worksheet name and rows against OOXML limits.

: (untyped name, untyped rows) -> Array

Parameters:

Returns:

  • (Array<String>)

    List of errors.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/xlsxrb/elements/worksheet.rb', line 206

def self.validate(name, rows)
  errs = []
  if name.nil? || !name.is_a?(String) || name.empty?
    errs << "worksheet name must be a non-empty String (got #{name.inspect})"
  else
    errs << "worksheet name cannot exceed 31 characters (got #{name.size})" if name.size > 31
    errs << "worksheet name cannot contain \\, /, ?, *, [, or ]" if name.match?(%r{[\\/?*\[\]]})
  end
  errs << "rows must be an Array (got #{rows.class})" unless rows.is_a?(Array)
  if rows.is_a?(Array)
    indices = rows.map(&:index)
    if indices.uniq.size != indices.size
      dups = indices.select { |i| indices.count(i) > 1 }.uniq
      errs << "duplicate row index: #{dups.join(", ")} — row indices within a sheet must be unique"
    end
  end
  errs
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Compare worksheets for equality. : (untyped other) -> bool

Parameters:

  • other (Object)

Returns:

  • (Boolean)


178
179
180
181
182
# File 'lib/xlsxrb/elements/worksheet.rb', line 178

def ==(other)
  return false unless other.is_a?(Worksheet)

  name == other.name && rows == other.rows && columns == other.columns && charts == other.charts
end

#deconstruct_keys(_keys) ⇒ Hash[Symbol, untyped]

Support pattern matching. : (Array?) -> Hash[Symbol, untyped]

Parameters:

  • (Array[Symbol], nil)

Returns:

  • (Hash[Symbol, untyped])


172
173
174
# File 'lib/xlsxrb/elements/worksheet.rb', line 172

def deconstruct_keys(_keys)
  { name: name, rows: rows, columns: columns, charts: charts, unmapped_data: unmapped_data, errors: errors }
end

#each {|row| ... } ⇒ Enumerator, void

Iterate over rows in the worksheet.

: () { (Elements::Row) -> void } -> void : | () -> Enumerator[Elements::Row, void]

Examples:

sheet.each do |row|
  puts row.to_a.inspect
end

Yields:

  • (row)

Yield Parameters:

Returns:

  • (Enumerator, void)


52
53
54
55
56
# File 'lib/xlsxrb/elements/worksheet.rb', line 52

def each(&)
  return to_enum(:each) unless block_given?

  rows.each(&)
end

#each_cell {|cell| ... } ⇒ Enumerator, void

Iterate over all cells across rows.

: () { (Elements::Cell) -> void } -> void : | () -> Enumerator[Elements::Cell, void]

Yields:

  • (cell)

Yield Parameters:

Returns:

  • (Enumerator, void)


85
86
87
88
89
# File 'lib/xlsxrb/elements/worksheet.rb', line 85

def each_cell(&)
  return to_enum(:each_cell) unless block_given?

  cells.each(&)
end

#each_row {|row| ... } ⇒ Enumerator, void

Iterate over rows in the worksheet.

: () { (Elements::Row) -> void } -> void : | () -> Enumerator[Elements::Row, void]

Examples:

sheet.each_row do |row|
  puts "Row #{row.index}: #{row.to_a.inspect}"
end

Yields:

  • (row)

Yield Parameters:

Returns:

  • (Enumerator, void)


71
72
73
74
75
# File 'lib/xlsxrb/elements/worksheet.rb', line 71

def each_row(&)
  return to_enum(:each_row) unless block_given?

  rows.each(&)
end

#hashInteger

: () -> Integer

Returns:

  • (Integer)


186
187
188
# File 'lib/xlsxrb/elements/worksheet.rb', line 186

def hash
  [self.class, name, rows, columns, charts].hash
end

#loadElements::Worksheet Also known as: to_worksheet

Returns self when load is called on an already in-memory Worksheet.

: () -> Elements::Worksheet

Returns:



195
196
197
# File 'lib/xlsxrb/elements/worksheet.rb', line 195

def load
  self
end

#update_cell(ref, value: nil, style_index: nil, formula: nil) ⇒ Worksheet

Returns a new Worksheet with the specified cell updated.

: (String ref, ?value: untyped, ?style_index: Integer | String | nil, ?formula: Elements::Formula?) -> Elements::Worksheet

Examples:

new_sheet = sheet.update_cell("B1", value: "Updated")

Parameters:

  • ref (String)

    The cell reference (e.g. "B1").

  • value (Object) (defaults to: nil)

    The new cell value.

  • style_index (Integer, String, nil) (defaults to: nil)

    Optional new style index.

  • formula (Elements::Formula, nil) (defaults to: nil)

    Optional new formula.

  • value: (Object) (defaults to: nil)
  • style_index: (Integer, String, nil) (defaults to: nil)
  • formula: (Elements::Formula, nil) (defaults to: nil)

Returns:



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
# File 'lib/xlsxrb/elements/worksheet.rb', line 111

def update_cell(ref, value: nil, style_index: nil, formula: nil)
  parsed = Cell.parse_ref(ref)
  raise ArgumentError, "invalid cell reference: #{ref}" unless parsed

  row_idx, col_idx = parsed
  existing_row = row_at(row_idx)

  if existing_row
    existing_cell = existing_row.cell_at(col_idx)
    new_cell = if existing_cell
                 existing_cell.with(
                   value: value || existing_cell.value,
                   style_index: style_index || existing_cell.style_index,
                   formula: formula || existing_cell.formula
                 )
               else
                 Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula)
               end

    # Replace cell in the existing row
    new_cells = existing_row.cells.reject { |c| c.column_index == col_idx }
    new_cells << new_cell
    new_cells.sort_by!(&:column_index)

    new_row = existing_row.with(cells: new_cells)
    new_rows = rows.map { |r| r.index == row_idx ? new_row : r }
  else
    # Row doesn't exist, create it
    new_cell = Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula)
    new_row = Row.new(index: row_idx, cells: [new_cell])
    new_rows = (rows + [new_row]).sort_by!(&:index)
  end
  with(rows: new_rows)
end

#valid?Boolean

Returns whether the worksheet is valid according to OOXML specifications.

: () -> bool

Returns:

  • (Boolean)


95
96
97
# File 'lib/xlsxrb/elements/worksheet.rb', line 95

def valid?
  errors.empty?
end

#with(**changes) ⇒ Worksheet

Returns a new Worksheet with attributes replaced (Data-like behavior).

: (**untyped) -> Elements::Worksheet

Parameters:

  • changes (Hash)
  • (Object)

Returns:



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/xlsxrb/elements/worksheet.rb', line 152

def with(**changes)
  new_name = changes.key?(:name) ? changes[:name] : name
  new_rows = changes.key?(:rows) ? changes[:rows] : rows
  new_cols = changes.key?(:columns) ? changes[:columns] : columns
  new_charts = changes.key?(:charts) ? changes[:charts] : charts
  new_unmapped = changes.key?(:unmapped_data) ? changes[:unmapped_data] : unmapped_data
  new_errors = changes.key?(:errors) ? changes[:errors] : errors

  self.class.new(
    name: new_name,
    rows: new_rows,
    columns: new_cols,
    charts: new_charts,
    unmapped_data: new_unmapped,
    errors: new_errors
  )
end