lib/rspreadsheet/row.rb in rspreadsheet-0.0.5 vs lib/rspreadsheet/row.rb in rspreadsheet-0.0.6

- old
+ new

@@ -1,17 +1,296 @@ require('rspreadsheet/cell') +include Forwardable # Currently this is only syntax sugar for cells and contains no functionality module Rspreadsheet +class RowArray + def initialize(aworksheet_node) + @worksheet_node = aworksheet_node + + # initialize @rowgroups from @worksheet_node + @rowgroups = [] + unless @worksheet_node.nil? + @worksheet_node.elements.select{|node| node.name == 'table-row'}.each do |row_source_node| + @rowgroups << prepare_row_group(row_source_node) # it is in @worksheet_node so suffices to add object to @rowgroups + end + end + end + def prepare_row_group(size_or_xmlnode) # appends new RowGroup at the end + # reading params + if size_or_xmlnode.kind_of? LibXML::XML::Node + size = (size_or_xmlnode['number-rows-repeated'] || 1).to_i + node = size_or_xmlnode + elsif size_or_xmlnode.to_i>0 + size = size_or_xmlnode.to_i + node = nil + else + return nil + end + index = first_unused_row_index + + # construct result + RowGroup.new(self,index..index+size-1,node).normalize + end + def add_row_group(size_or_xmlnode) + result = prepare_row_group(size_or_xmlnode) + @rowgroups << result + @worksheet_node << result.xmlnode + result + end + def get_row_group(rowi) + @rowgroups.find{ |rowgroup| rowgroup.range.cover?(rowi) } + end + def get_row(rowi) + rg = get_row_group(rowi).andand.normalize + case rg + when SingleRow then rg + when RowGroup then MemberOfRowGroup.new(rowi, rg) + when nil + if rowi>0 then UninitializedEmptyRow.new(self,rowi) else nil end + else raise + end + end + # prolonges the RowArray to cantain rowi and returns it + def detach_of_bound_row_group(rowi) + fill_row_group_size = rowi-first_unused_row_index + if fill_row_group_size>0 + add_row_group(fill_row_group_size) + end + add_row_group(1) + return get_row(rowi) + end + def first_unused_row_index + if @rowgroups.empty? + 1 + else + @rowgroups.last.range.end+1 + end + end + # This detaches row rowi from the group and perhaps splits the RowGroup + # into two pieces. This makes the row individually editable. + def detach(rowi) + index = get_row_group_index(rowi) + row_group = @rowgroups[index] + range = row_group.range + + # prepare new components + replaceby = [] + replaceby << RowGroup.new(self,range.begin..rowi-1) + replaceby << (result = SingleRow.new(self,rowi)) + replaceby << RowGroup.new(self,rowi+1..range.end) + + # put original range somewhere in replaceby and shorten it + if rowi>range.begin + replaceby[0] = row_group + row_group.range = range.begin..rowi-1 + else + replaceby[2] = row_group + row_group.range = rowi+1..range.end + end + + # normalize and delete empty parts + replaceby = replaceby.map(&:normalize).compact + + # do the replacement in xml + marker = LibXML::XML::Node.new('temporarymarker') + row_group.xmlnode.next = marker + row_group.xmlnode.remove! + replaceby.each{ |rg| + marker.prev = rg.xmlnode + } + marker.remove! + + # do the replacement in array + @rowgroups[index..index]=replaceby + result + end + + private + def get_row_group_index(rowi) + @rowgroups.find_index{ |rowgroup| rowgroup.range.cover?(rowi) } + end +end + class Row - def initialize(workbook,rowi) - @rowi = rowi - @workbook = workbook + @readonly = :unknown + # ? @rowindex + def self.empty_row_node + LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table')) end +end + +class RowWithXMLNode < Row + attr_accessor :xmlnode + def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end def cells(coli) - @workbook.cells(@rowi,coli) + coli = coli.to_i + return nil if coli.to_i<=0 + Cell.new(self,coli,cellnodes(coli)) end + def nonemptycells + nonemptycellsindexes.collect{ |index| cells(index) } + end + def nonemptycellsindexes + used_col_range.to_a.select do |coli| + cellnode = cellnodes(coli) + !(cellnode.content.nil? or cellnode.content.empty? or cellnode.content =='') or + !cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty? + end + end + def used_col_range + 1..first_unused_column_index-1 + end + def cellnodes(coli) + cellnode = nil + while true + curr_coli=1 + cellnode = @xmlnode.elements.select{|n| n.name=='table-cell'}.find do |el| + curr_coli += (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i + curr_coli > coli + end + unless cellnode.nil? + return cellnode + else + add_cell + end + end + end + def add_cell(repeated=1) + cell = Cell.new(self,first_unused_column_index) + Tools.set_ns_attribute(cell.xmlnode,'table','number-columns-repeated',repeated) if repeated>1 + @xmlnode << cell.xmlnode + cell + end + def used_range + fu = first_unused_column_index + (fu>1) ? 1..fu : nil + end + def first_unused_column_index + 1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el| + sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i + end + end end +class RowGroup < RowWithXMLNode + @readonly = :yes_always + attr_reader :range + attr_accessor :parent_array, :xmlnode + def initialize(aparent_array,arange,axmlnode=nil) + @parent_array = aparent_array + @range = arange + if axmlnode.nil? + axmlnode = Row.empty_row_node + Tools.set_ns_attribute(axmlnode,'table','number-rows-repeated',range.size) if range.size>1 + end + @xmlnode = axmlnode + end + # returns SingleRow if size of range is 1 and nil if it is 0 or less + def normalize + case range.size + when 2..Float::INFINITY then self + when 1 then SingleRow.new_from_rowgroup(self) + else nil + end + end + def repeated; range.size end + def repeated?; range.size>1 end + def range=(arange) + @range=arange + Tools.set_ns_attribute(@xmlnode,'table','number-rows-repeated',range.size, 1) + end end + +class SingleRow < RowWithXMLNode + @readonly = :no + attr_accessor :xmlnode + # index Integer + def initialize(aparent_array,aindex,axmlnode=nil) + @parent_array = aparent_array + @index = aindex + if axmlnode.nil? + axmlnode = Row.empty_row_node + end + @xmlnode = axmlnode + end + def self.new_from_rowgroup(rg) + anode = rg.xmlnode + Tools.remove_ns_attribute(anode,'table','number-rows-repeated') + + SingleRow.new(rg.parent_array,rg.range.begin,anode) + end + def normalize; self end + def repeated?; false end + def repeated; 1 end + def range; (@index..@index) end + def detach; true end + def row; @index end + +end + +class LazyDetachableRow < Row + @readonly = :yes_but_detachable + def initialize(rowi) + @index = rowi.to_i + end + def add_cell; detach.add_cell end + def style_name=(value); detach.style_name=value end + def row; @index end +end + +## there are not data in this object, they are taken from RowGroup, but this is only readonly +class MemberOfRowGroup < LazyDetachableRow + @readonly = :yes_but_detachable + extend Forwardable + delegate [:repeated?, :repeated, :xmlnode, :parent_array] => :@row_group + attr_accessor :row_group # for dubugging + + # @index Integer + # @row_group RepeatedRow + def initialize(arowi,arow_group) + super(arowi) + @row_group = arow_group + raise 'Wrong parameter given' unless @row_group.is_a? RowGroup + end + def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup + @row_group.parent_array.detach(@index) + end + def cells(coli) + Cell.new(self,coli,@row_group.cellnodes(coli)).tap{|n| n.mode = :repeated} + end + def nonemptycells + @row_group.nonemptycellsindexes.collect{ |coli| cells(coli) } + end +end + +## this is a row outside the used bounds. the main purpose of this object is to magically synchronize to existing data, once they are created +class UninitializedEmptyRow < LazyDetachableRow + @readonly = :yes_but_detachable + attr_reader :parent_array # debug only + def initialize(aparent_array,arowi) + super(arowi) + @parent_array = aparent_array + end + def cells(coli) + if still_out_of_used_range? + Cell.new(self,coli,Cell.empty_cell_node).tap{|n| n.mode = :outbound} + else + @parent_array.get_row(@index).cells(coli) + end + end + def normalize + if still_out_of_used_range? + self + else + @parent_array.get_row(@index) + end + end + def detach; @parent_array.detach_of_bound_row_group(@index) end + def still_out_of_used_range?; @index >= @parent_array.first_unused_row_index end + def xmlnode; Row.empty_row_node end + def nonemptycells; [] end +end + +end \ No newline at end of file