Class: LiquidXlsx::RenderState

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

Overview

Tracks merge cells, formula expansions, and block boundaries.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(merge_transformer, original_merges, formula_translator) ⇒ RenderState

Returns a new instance of RenderState.



613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/liquid_xlsx/renderer.rb', line 613

def initialize(merge_transformer, original_merges, formula_translator)
  @merge_transformer = merge_transformer
  @original_merges = original_merges
  @formula_translator = formula_translator

  # Map: template_row_num => [new_row_num, ...] (multiple for loop iterations)
  @row_map = {}
  # Structural block boundaries (template row numbers) for merge crossing detection
  @blocks = []
  # Current loop state
  # Each frame: { iterations: [...], current_iteration: {row_map: {}, index:} or nil }
  @loop_stack = []
end

Instance Attribute Details

#formula_translatorObject (readonly)

Returns the value of attribute formula_translator.



611
612
613
# File 'lib/liquid_xlsx/renderer.rb', line 611

def formula_translator
  @formula_translator
end

Instance Method Details

#build_merge_rangesObject



702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/liquid_xlsx/renderer.rb', line 702

def build_merge_ranges
  result = []

  @original_merges.each do |merge_info|
    ref = merge_info[:ref]
    start_template_row = merge_info[:start_row]
    end_template_row = merge_info[:end_row]

    # Skip merges on structural rows (consumed by parser)
    unless @row_map.key?(start_template_row) || @row_map.key?(end_template_row)
      next
    end

    if @row_map.key?(start_template_row) && @row_map.key?(end_template_row)
      start_rows = @row_map[start_template_row]
      end_rows = @row_map[end_template_row]

      start_rows.each_with_index do |sr, i|
        er = end_rows[i] || sr
        cloned = clone_merge(ref, sr - start_template_row, er - end_template_row)
        result << cloned if cloned
      end
    elsif @row_map.key?(start_template_row)
      @row_map[start_template_row].each do |nr|
        cloned = clone_merge_row(ref, nr - start_template_row)
        result << cloned if cloned
      end
    else
      result << ref
    end
  end

  result
end

#check_block_boundaries(sheet_name) ⇒ Object

Check if any merge cell crosses a structural block boundary. Both block bounds and merge bounds use template row numbers.



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
# File 'lib/liquid_xlsx/renderer.rb', line 684

def check_block_boundaries(sheet_name)
  @original_merges.each do |merge_info|
    merge_start = merge_info[:start_row]
    merge_end = merge_info[:end_row]

    @blocks.each do |block|
      next unless crosses_boundary?(merge_start, merge_end, block[:start], block[:end])

      raise UnsupportedTemplateError.new(
        "Merged cell '#{merge_info[:ref]}' crosses a #{block[:type]} block boundary " \
        "(template rows #{block[:start]}-#{block[:end]})",
        sheet: sheet_name,
        block_rows: "rows #{block[:start]}-#{block[:end]}"
      )
    end
  end
end

#current_pathObject

Current iteration path: indices of all active loop iterations. Used for formula-path and expansion-path visibility filtering.



678
679
680
# File 'lib/liquid_xlsx/renderer.rb', line 678

def current_path
  @loop_stack.filter_map { |f| f[:current_iteration]&.dig(:index) }
end

#end_iterationObject



657
658
659
660
# File 'lib/liquid_xlsx/renderer.rb', line 657

def end_iteration
  @loop_stack.last[:iterations] << @loop_stack.last[:current_iteration]
  @loop_stack.last[:current_iteration] = nil
end

#end_loopObject

Aggregate per-iteration row maps of the current loop execution into a single => [rendered_row, ...] hash and pop the frame.



664
665
666
667
668
669
670
671
672
673
674
# File 'lib/liquid_xlsx/renderer.rb', line 664

def end_loop
  frame = @loop_stack.pop
  aggregate = {}
  frame[:iterations].each do |iter|
    iter[:row_map].each do |t_row, r_rows|
      aggregate[t_row] ||= []
      aggregate[t_row].concat(r_rows)
    end
  end
  aggregate
end

#enter_block(type, for_row, endfor_row) ⇒ Object



641
642
643
# File 'lib/liquid_xlsx/renderer.rb', line 641

def enter_block(type, for_row, endfor_row)
  @blocks << { type: type, start: for_row, end: endfor_row } if for_row && endfor_row
end

#leave_blockObject



645
646
647
# File 'lib/liquid_xlsx/renderer.rb', line 645

def leave_block
  # Block already recorded in enter_block
end

#record_row(template_row_num, new_row_num) ⇒ Object



627
628
629
630
631
632
633
634
635
636
637
638
639
# File 'lib/liquid_xlsx/renderer.rb', line 627

def record_row(template_row_num, new_row_num)
  @row_map[template_row_num] ||= []
  @row_map[template_row_num] << new_row_num

  # Also write to all active per-iteration row maps for later aggregation
  @loop_stack.each do |frame|
    iter = frame[:current_iteration]
    next unless iter

    iter[:row_map][template_row_num] ||= []
    iter[:row_map][template_row_num] << new_row_num
  end
end

#start_iteration(index) ⇒ Object



653
654
655
# File 'lib/liquid_xlsx/renderer.rb', line 653

def start_iteration(index)
  @loop_stack.last[:current_iteration] = { row_map: {}, index: index }
end

#start_loopObject



649
650
651
# File 'lib/liquid_xlsx/renderer.rb', line 649

def start_loop
  @loop_stack.push(iterations: [], current_iteration: nil)
end