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.



618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/liquid_xlsx/renderer.rb', line 618

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.



616
617
618
# File 'lib/liquid_xlsx/renderer.rb', line 616

def formula_translator
  @formula_translator
end

Instance Method Details

#build_merge_rangesObject



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
736
737
738
739
740
# File 'lib/liquid_xlsx/renderer.rb', line 707

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.



689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
# File 'lib/liquid_xlsx/renderer.rb', line 689

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.



683
684
685
# File 'lib/liquid_xlsx/renderer.rb', line 683

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

#end_iterationObject



662
663
664
665
# File 'lib/liquid_xlsx/renderer.rb', line 662

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.



669
670
671
672
673
674
675
676
677
678
679
# File 'lib/liquid_xlsx/renderer.rb', line 669

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



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

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



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

def leave_block
  # Block already recorded in enter_block
end

#record_row(template_row_num, new_row_num) ⇒ Object



632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/liquid_xlsx/renderer.rb', line 632

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



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

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

#start_loopObject



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

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