lib/rspreadsheet/row.rb in rspreadsheet-0.1.1 vs lib/rspreadsheet/row.rb in rspreadsheet-0.2.0
- old
+ new
@@ -1,138 +1,283 @@
require('rspreadsheet/cell')
require('forwardable')
-
# Currently this is only syntax sugar for cells and contains no functionality
module Rspreadsheet
-class RowArray
- def initialize(aworksheet,aworksheet_node)
+class Row < RowOrNode
+ attr_reader :worksheet, :rowi
+ def initialize(aworksheet,arowi)
@worksheet = aworksheet
- @worksheet_node = aworksheet_node
+ @rowi = arowi
+ end
+ def xmlnode; @worksheet.rowxmlnode(@rowi) end
+ def cells(coli)
+ coli = coli.to_i
+ if coli.to_i<=0
+ nil
+ else
+ Cell.new(@worksheet,@rowi,coli)
+ end
+ end
+ def self.empty_xmlnode(repeats=1)
+ node = LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
+ Tools.set_ns_attribute(node,'table','number-rows-repeated',repeats, 1)
+ node
+ end
+ def detach_if_needed;
+ detach if repeated?
+ end
+ def detach
+ @worksheet.detach_row_in_xml(@rowi)
+ self
+ end
+ def repeated
+ (Tools.get_ns_attribute_value(self.xmlnode, 'table', 'number-rows-repeated') || 1).to_i
+ end
+ def repeated?; mode==:repeated || mode==:outbound end
+ alias :is_repeated? :repeated?
+ def style_name=(value);
+ detach_if_needed
+ Tools.set_ns_attribute(xmlnode,'table','style-name',value)
+ end
+ def used_range
+ @worksheet.rowrange(@rowi)
+ end
+ def nonemptycells
+ nonemptycellsindexes.collect{ |index| cells(index) }
+ end
+ def nonemptycellsindexes
+ @worksheet.row_nonempty_cells_col_indexes(@rowi)
+ end
+end
- # 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
+# class Row
+# def initialize
+# @readonly = :unknown
+# @cells = {}
+# end
+# def worksheet; @parent_array.worksheet end
+# def parent_array; @parent_array end # for debug only
+# def used_col_range; 1..first_unused_column_index-1 end
+# def used_range; used_col_range end
+# def first_unused_column_index; raise 'this should be redefined in subclasses' end
+# end
+
+
+# --------------------------
+
+
+# XmlTiedArrayItemGroup is internal representation of repeated items in XmlTiedArray.
+class XmlTiedArrayItemGroup
+# extend Forwardable
+# delegate [:normalize ] => :@row_group
+
+ def normalize; @rowgroup.normalize end
+ def range; @rowgroup.range end
+ def repeated?; self.range.size>1 end
+ def xmlnode; @rowgroup.xmlnode end
+
+ def initialize(aparent_array,arange,axmlnode=nil)
+ @rowgroup = RowGroup.new(aparent_array,arange,axmlnode)
+ end
+ def self.new_from_xml
+ end
+ def to_rowgroup
+ @rowgroup
+ end
+ def range=(arange)
+
+ end
+end
+
+# array which synchronizes with xml structure and reflects. number-xxx-repeated attributes
+# also caches returned objects for indexes.
+# options must contain
+# :xml_items, :xml_repeated_attribute, :object_type
+
+class XmlTiedArray < Array
+ def initialize(axmlnode, options={}) # TODO get rid of XmlTiedArray
+ @xmlnode = axmlnode
+ @options = options
+
+ missing_options = [:xml_repeated_attribute,:xml_items_node_name,:object_type]-@options.keys
+ raise "Some options missing (#{missing_options.inspect})" unless missing_options.empty?
+
+ unless @xmlnode.nil?
+ @xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |group_source_node|
+ self << parse_xml_to_group(group_source_node) # it is in @xmlnode so suffices to add object to @rowgroups
end
end
+ @itemcache=Hash.new()
end
- def prepare_row_group(size_or_xmlnode) # appends new RowGroup at the end
+ def parse_xml_to_group(size_or_xmlnode) # parses xml to new RowGroup which can be added at the end
# reading params
if size_or_xmlnode.kind_of? LibXML::XML::Node
- size = (size_or_xmlnode['number-rows-repeated'] || 1).to_i
+ size = (size_or_xmlnode[@options[:xml_repeated_attribute]] || 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
-
+ index = first_unused_index
# construct result
- RowGroup.new(self,index..index+size-1,node).normalize
+ Rspreadsheet::XmlTiedArrayItemGroup.new(self,index..index+size-1,node)
end
- def add_row_group(size_or_xmlnode)
- result = prepare_row_group(size_or_xmlnode)
- @rowgroups << result
- @worksheet_node << result.xmlnode
+ def add_item_group(size_or_xmlnode)
+ result = parse_xml_to_group(size_or_xmlnode)
+ self << result
+ @xmlnode << result.xmlnode
result
end
- def get_row_group(rowi)
- @rowgroups.find{ |rowgroup| rowgroup.range.cover?(rowi) }
+ def first_unused_index
+ empty? ? 1 : last.range.end+1
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
+ def detach_of_bound_item(index)
+ fill_row_group_size = index-first_unused_index
if fill_row_group_size>0
- add_row_group(fill_row_group_size)
+ add_item_group(fill_row_group_size)
end
- add_row_group(1)
- return get_row(rowi)
+ add_item_group(1)
+ get_item(index) # aby se odpoved nacacheovala
end
- def first_unused_row_index
- if @rowgroups.empty?
- 1
+ def get_item_group(index)
+ find{ |item_group| item_group.range.cover?(index) }
+ end
+ def detach_item(index); get_item(index) end # TODO předělat do lazy podoby, kdy tohle nebude stejny
+ def get_item(index)
+ if index>= first_unused_index
+ nil
else
- @rowgroups.last.range.end+1
+ @itemcache[index] ||= Rspreadsheet::XmlTiedArrayItem.new(self,index)
end
end
- # This detaches row rowi from the group and perhaps splits the RowGroup
+ # This detaches item index 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
+ def detach(index)
+ group_index = get_group_index(index)
+ item_group = self[group_index]
+ range = item_group.range
+ return self if range==(index..index)
# 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)
+ replaceby << RowGroup.new(self,range.begin..index-1)
+ replaceby << (result = SingleRow.new(self,index))
+ replaceby << RowGroup.new(self,index+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
+
+ if index>range.begin
+ replaceby[0] = item_group
+ item_group.range = range.begin..index-1
else
- replaceby[2] = row_group
- row_group.range = rowi+1..range.end
+ replaceby[2] = item_group
+ item_group.range = index+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!
+ item_group.xmlnode.next = marker
+ item_group.xmlnode.remove!
replaceby.each{ |rg|
marker.prev = rg.xmlnode
}
marker.remove!
# do the replacement in array
- @rowgroups[index..index]=replaceby
+ self[group_index..group_index]=replaceby
result
end
- def worksheet; @worksheet end
- private
- def get_row_group_index(rowi)
- @rowgroups.find_index{ |rowgroup| rowgroup.range.cover?(rowi) }
+ private
+ def get_group_index(index)
+ self.find_index{ |rowgroup| rowgroup.range.cover?(index) }
end
end
-class Row
- @readonly = :unknown
- # ? @rowindex
- def self.empty_row_node
- LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
+class XmlTiedArrayItem
+ attr_reader :index
+ def initialize(aarray,aindex)
+ @array = aarray
+ @index = aindex
+ if self.virtual?
+ @object = nil
+ else
+ @object = @array.options[:object_type].new(group.xmlnode)
+ end
end
- def worksheet; @parent_array.worksheet end
- def parent_array; @parent_array end # for debug only
+ def group; @array.get_item_group(index) end
+ def repeated?; group.repeated? end
+ def virtual?; ! self.repeated? end
+ def array
+ raise 'Group empty' if @group.nil?
+ @array
+ end
end
+class RowArray < XmlTiedArray
+ attr_reader :row_array_cache
+ def initialize(aworksheet,aworksheet_node)
+ @worksheet = aworksheet
+ @row_array_cache = Hash.new()
+ super(aworksheet_node, :xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated', :object_type=>Row)
+ end
+ def get_row(rowi)
+ if @row_array_cache.has_key?(rowi)
+ return @row_array_cache[rowi]
+ end
+ item = self.get_item(rowi)
+ @row_array_cache[rowi] = if item.nil?
+ if rowi>0 then Rspreadsheet::UninitializedEmptyRow.new(self,rowi) else nil end
+ else
+ if item.repeated?
+ Rspreadsheet::MemberOfRowGroup.new(item.index, item.group.to_rowgroup)
+ else
+ Rspreadsheet::SingleRow.new_from_rowgroup(item.group.to_rowgroup)
+ end
+ end
+ end
+ # aliases
+ def first_unused_row_index; first_unused_index end
+ def worksheet; @worksheet end
+ def detach_of_bound_row_group(index)
+ super(index)
+ return get_row(index)
+ end
+end
+
+# class Row
+# def initialize
+# @readonly = :unknown
+# @cells = {}
+# end
+# def self.empty_row_node
+# LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
+# end
+# def worksheet; @parent_array.worksheet end
+# def parent_array; @parent_array end # for debug only
+# def used_col_range; 1..first_unused_column_index-1 end
+# def used_range; used_col_range end
+# def first_unused_column_index; raise 'this should be redefined in subclasses' end
+# def cells(coli)
+# coli = coli.to_i
+# return nil if coli.to_i<=0
+# @cells[coli] ||= get_cell(coli)
+# 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)
- coli = coli.to_i
- return nil if coli.to_i<=0
+ def get_cell(coli)
Cell.new(self,coli,cellnodes(coli))
end
def nonemptycells
nonemptycellsindexes.collect{ |index| cells(index) }
end
@@ -141,13 +286,10 @@
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|
@@ -165,14 +307,10 @@
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
@@ -181,10 +319,11 @@
class RowGroup < RowWithXMLNode
@readonly = :yes_always
attr_reader :range
attr_accessor :parent_array, :xmlnode
def initialize(aparent_array,arange,axmlnode=nil)
+ super()
@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
@@ -210,35 +349,36 @@
class SingleRow < RowWithXMLNode
@readonly = :no
attr_accessor :xmlnode
# index Integer
def initialize(aparent_array,aindex,axmlnode=nil)
+ super()
@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 detach; self end
def row; @index end
-
+ def still_out_of_used_range?; false end
end
class LazyDetachableRow < Row
@readonly = :yes_but_detachable
def initialize(rowi)
+ super()
@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
@@ -254,18 +394,23 @@
# @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
+ raise 'Wrong parameter given - class is '+@row_group.class.to_a 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}
+ def get_cell(coli)
+ c = Cell.new(self,coli,@row_group.cellnodes(coli))
+ c.mode = :repeated
+ c
end
+ def first_unused_column_index
+ @row_group.first_unused_column_index
+ end
def nonemptycells
@row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
end
end
@@ -275,13 +420,13 @@
attr_reader :parent_array # debug only
def initialize(aparent_array,arowi)
super(arowi)
@parent_array = aparent_array
end
- def cells(coli)
+ def get_cell(coli)
if still_out_of_used_range?
- Cell.new(self,coli,Cell.empty_cell_node).tap{|n| n.mode = :outbound}
+ Cell.new(self,coli,nil)
else
@parent_array.get_row(@index).cells(coli)
end
end
def normalize
@@ -289,12 +434,15 @@
self
else
@parent_array.get_row(@index)
end
end
- def detach; @parent_array.detach_of_bound_row_group(@index) end
+ def detach; @parent_array.detach_item(@index) end
+ def detach_cell(col)
+ self.detach.detach_cell(col)
+ 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
+ def first_unused_column_index; 1 end
end
end
\ No newline at end of file