module Rspreadsheet::XMLTiedArray

Abstract class representing and array which is tied to a particular element of XML file.
It uses cashing to make access to array more effective. Implements the following methods:

  * subitems(index) - returns subitem object on index 
  * subitems - returns array of all subitems. Please note that first item is always nil so 
    the array can be accessed using 1-based indexes.

Importer must provide:

  * prepare_subitem(aindex) - must return newly created object representing item on aindex
  * delete - ???
  * xmlnode - must return xmlnode to which the array is tied. If speed is not a concern, 
              consider not cashing it into variable, but finding it through document or parent. 
              This prevents "broken" links. Sometimes when array is empty, the node does note
              necessarily exists. That is fine, XMLTiedArray behaves correctly even with nil xmlnode,
              of course util you want to insert something. If this may happens, importer must
              provide method prepare_empty_xmlnode which prepares (and returns) empty xml node.
              It is lazy called, as late as possible.
  * subnode_options - returns hash of options used to locate subitems in xml with these values
    * subnode_options[:node_name] - how the relevant subitems are named (string)
    * subnode_options[:alt_node_names] - array of strings of alternative names to :node_name
      these are recognized in searching, but never created when creating new node.
    * subnode_options[:node_namespace] - namespace of relevant subitems (defaults to table)
    * subnode_options[:repeated_attribute] - attribute of elements which tell how many 
      times this is repeated (this is only used in XMLTiedArray_WithRepeatableItems)
    * subnode_options[:ignore_groupings] - some subnodes can rather be groups of subnodes, these
      groups need to be expanded and nodes put out of them 
  * intilize must call initialize_xml_tied_array

Notes for developers

  * This class is made to be included.
  * Terminology
    * item, subitem is object from @itemcache (quite often subclass of XMLTiedItem)
    * node, subnode is LibXML::XML::Node object
  * usual flow is that when user asks for an item the proxy item object is created (prepare_item) which
    only contains index etx, but no values. When the values are needed, it asks its parent to get the xmlnode, it first 
    uses xmlsubnodes method to get all sumbodes and then by respecting the repeating finds apropriate node.
  * Beware that the implementation of methods needs to be done in a way that it continues to
    work when items are "repeatable" - see XMLTiedArray_WithRepeatableItems. When impractical or impossible
    please implement the corresponding method in XMLTiedArray_WithRepeatableItems or at least override it there
    and make it raise exception.

@private

Attributes

itemcache[R]

Public Instance Methods

first_unused_subitem_index() click to toggle source

Finds first unused subitem index

# File lib/rspreadsheet/xml_tied_array.rb, line 104
def first_unused_subitem_index
  (1 + xmlsubnodes.sum { |node| how_many_times_node_is_repeated(node) }).to_i
end
how_many_times_node_is_repeated(node) click to toggle source

@!group other subitems methods This is used (i.e. in first_unused_subitem_index) so it is flexible and can be reused in XMLTiedArray_WithRepeatableItems @private

# File lib/rspreadsheet/xml_tied_array.rb, line 128
def how_many_times_node_is_repeated(node); 1 end
initialize_xml_tied_array() click to toggle source
# File lib/rspreadsheet/xml_tied_array.rb, line 61
def initialize_xml_tied_array
  @itemcache = Hash.new
end
insert_new_empty_subitem_before(aindex)
Alias for: insert_new_item
insert_new_empty_subnode_before(aindex) click to toggle source

@!group inserting new subnodes TODO: refactor out repeatable connected code

# File lib/rspreadsheet/xml_tied_array.rb, line 141
def insert_new_empty_subnode_before(aindex)
  node_after = my_subnode(aindex)
  if !node_after.nil?
    node_after.prev = prepare_empty_subnode
    return node_after.prev
  elsif aindex==size+1
    # check whether xmlnode is ready for insetion
    if xmlnode.nil?
      prepare_empty_xmlnode
      if xmlnode.nil?
        raise 'Attempted call prepare_empty_xmlnode, but it did not created xmlnode correctly (it is still nil).'
      end
    end
    # do the insertion
    xmlnode <<  prepare_empty_subnode
    return xmlnode.last
  else
    raise IndexError.new("Index #{aindex} out of bounds (1..#{self.size})")
  end
end
insert_new_item(aindex) click to toggle source

@!group inserting new items

Inserts empty subitem at the index position. Item currently on this position and all items after are shifter by index one.

# File lib/rspreadsheet/xml_tied_array.rb, line 111
def insert_new_item(aindex)
  @itemcache.keys.sort.reverse.select{|i| i>=aindex }.each do |i| 
    @itemcache[i+1]=@itemcache.delete(i)
    @itemcache[i+1]._shift_by(1)
  end
  insert_new_empty_subnode_before(aindex)  # nyní vlož node do xml
  @itemcache[aindex] =  subitem(aindex)
end
last() click to toggle source
# File lib/rspreadsheet/xml_tied_array.rb, line 78
def last
  subitem(size)
end
lenght()
Alias for: size
length()
Alias for: size
my_subnode(aindex) click to toggle source
# @!group accessing subnodes

returns xmlnode with index does NOT respect repeated_attribute

# File lib/rspreadsheet/xml_tied_array.rb, line 135
def my_subnode(aindex)
  raise 'Using method which does not respect repeated_attribute with options that are using it. You probably donot want to do that.' unless subnode_options[:repeated_attribute].nil?
  return xmlsubnodes[aindex-1]
end
prepare_empty_subnode() click to toggle source
# File lib/rspreadsheet/xml_tied_array.rb, line 162
def prepare_empty_subnode
  Tools.prepare_ns_node(
    subnode_options[:node_namespace] || 'table',
    subnode_options[:node_name]
  )
end
prepare_empty_xmlnode() click to toggle source

importer must provide this only if it may happen that xmlnode is empty AND we will want to insert subitems

# File lib/rspreadsheet/xml_tied_array.rb, line 172
def prepare_empty_xmlnode
  raise 'xmlnode is empty and I do not know how to create empty xmlnode. Please provide prepare_empty_xmlnode method in your object.'
end
push_new() click to toggle source
# File lib/rspreadsheet/xml_tied_array.rb, line 121
def push_new
  insert_new_item(first_unused_subitem_index)
end
size() click to toggle source

Number of subitems

# File lib/rspreadsheet/xml_tied_array.rb, line 99
def size; first_unused_subitem_index-1 end
Also aliased as: length, lenght
subitem(aindex) click to toggle source

Returns item with index aindex

# File lib/rspreadsheet/xml_tied_array.rb, line 68
def subitem(aindex)
  aindex = aindex.to_i
  if aindex.to_i<=0
    raise 'Item index should be greater then 0' if Rspreadsheet.raise_on_negative_coordinates
    nil 
  else 
    @itemcache[aindex] ||= prepare_subitem(aindex)
  end
end
subitems(*params) click to toggle source

Returns an array of subitems (when called without parameter) or an item on paricular index (when called with parameter).

# File lib/rspreadsheet/xml_tied_array.rb, line 83
def subitems(*params)
  case params.length 
    when 0 then subitems_array
    when 1 then subitem(params[0]) 
    else raise ArgumentError.new('Wrong number of arguments.')
  end
end
subitems_array() click to toggle source

Returns array of subitems (repeated friendly)

# File lib/rspreadsheet/xml_tied_array.rb, line 92
def subitems_array
  (1..self.size).collect do |i|
    subitem(i)
  end
end
xmlsubnodes() click to toggle source

@!group finding and accessing subnodes array containing subnodes of xmlnode which represent subitems

# File lib/rspreadsheet/xml_tied_array.rb, line 178
def xmlsubnodes
  axmlnode = self.xmlnode
  return [] if axmlnode.nil?
  node_name = subnode_options[:node_name]
  alt_node_names = subnode_options[:alt_node_names] || []
  ignore_groupings = subnode_options[:ignore_groupings] || []

  result = []
  axmlnode.children.each do |node|
    if ignore_groupings.include?(node.andand.name)
      node.children.each do |subnode|
        result << subnode
      end
    else
      result << node
    end
  end

  result.select do |node|
    node.element? &&                        # nejde o textový node
    ( (node_name == node.andand.name) ||    # a jde o node s pořadovaným názvem
      alt_node_names.include?(node.andand.name) )  # nebo s alternativním přípustným názvem
  end
end